Java多线程基础
1.1进程和线程的概念
1.什么是进程?
进程是收操作系统管理的基本单元。
我们可以看到,一个exe程序就可以看做是一个进程。
2.什么是线程?
线程就是在进程中独立运行的子任务。
例如我们使用QQ时,可以一边聊天,一边传输文件,一边添加好友等等。这些都可以看做是一个独立的线程。
1.2使用多线程
每一个进程都至少有一个线程在运行它。那么我们平常写的小程序有线程吗?当然有,这个线程就是main线程。
public class Test{
public static void main(String[] args){
//获取当前线程的名称
System.out.println(Thread.currentThread().getName());
/*
*运行结果main
*事实上这个线程就叫main,但是它和main方法没有任何关系,只是名字相同而已
*/
}
}
Java中创建线程的方式主要有两种:(一)继承Thread类,(二)实现Runnable接口
1.2.1 继承Thread类
public class MyThread extends Thread {
@Override
public void run() {
super.run();
System.out.println("MyThread");
}
}
public class MyThreadTest {
public static void main(String[] args) {
//创建线程对象
MyThread mt = new MyThread();
//调用start方法启动线程
mt.start();
System.out.println("运行结束");
/*
* 从运行结果上看,在使用多线程的时候,代码运行的结果和代码的顺序或调用顺序是没有关系的
* */
//mt.start();//错误写法,java.lang.IllegalThreadStateException
/*
* 注意事项:
* 不能多次调用start方法,会抛出IllegalThreadStateException
* 多次调用运行结果如右下图
* */
}
}
从运行结果上看,在使用多线程的时候,代码运行的结果和代码的顺序或调用顺序是没有关系的。也就是说在多线程的时候,方法的执行顺序是不确定的。
注意事项:start方法不能多次启动一个线程,否则会抛出异常IllegalThreadStateException。
public class MyThreadRandom extends Thread {
@Override
public void run() {
try{
for (int i = 0; i < 10; i++) {
int time = (int)(Math.random()*1000);
Thread.sleep(time);
System.out.println("run:"+Thread.currentThread().getName());
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public class MyThreadRandomTest {
public static void main(String[] args) {
MyThreadRandom mtr = new MyThreadRandom();
mtr.setName("myThreadRandom");
mtr.start();
// mtr.run();
try{
for (int i = 0; i < 10; i++) {
int time = (int)(Math.random()*1000);
Thread.sleep(time);
System.out.println("main:"+Thread.currentThread().getName());
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
调用start方法
调用run方法
从上面调用start方法运行结果,可以看到,多线程在线程的调用顺序上也是随机的。
注意事项:多线程的启动时调用start方法,而不是调用run方法。调用start方法,就是告诉“线程规划器”,我已经准备好运行run方法了。这时等待该线程的运行即可。这是异步的;如果调用run方法,那么此线程对象没有交给线程规划器,而是由main线程运行,所以这时候它不是异步的,而是同步的。
public class MyThreadSequence extends Thread {
private int i;
public MyThreadSequence(int i){
super();
this.i=i;
}
@Override
public void run() {
System.out.println(i);
}
}
public class MyThreadSequenceTest {
public static void main(String[] args) {
MyThreadSequence t1 = new MyThreadSequence(1);
MyThreadSequence t2 = new MyThreadSequence(2);
MyThreadSequence t3 = new MyThreadSequence(3);
MyThreadSequence t4 = new MyThreadSequence(4);
MyThreadSequence t5 = new MyThreadSequence(5);
MyThreadSequence t6 = new MyThreadSequence(6);
MyThreadSequence t7 = new MyThreadSequence(7);
MyThreadSequence t8 = new MyThreadSequence(8);
MyThreadSequence t9 = new MyThreadSequence(9);
MyThreadSequence t10 = new MyThreadSequence(10);
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
t7.start();
t8.start();
t9.start();
t10.start();
}
}
从运行结果上看,执行start方法的顺序也不代表线程启动的顺序。
总结:
多线程中:
1.代码的顺序不代表执行的顺序。
2.多线程程序,线程的调用也是随机的。
3.执行start方法的顺序不代表线程启动的顺序。
注意事项:
1.不要多次对一个线程对象调用start方法,会抛出IllegalThreadStateException.
2.直接调用重写的run方法是没有用的,那样不是异步效果,而是同步效果。
1.2.2实现Runnable接口
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("运行中");
}
}
public class MyRunnableTest {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread thread = new Thread(mr);
thread.start();
System.out.println("运行结束!");
}
}
创建多线程的第二种方式:实现Runnable接口。由于Java是单继承,不能多继承,所以当某个类已经继承了另外一个类时,如果想要实现多线程的方式,就可以使用实现Runnable接口的方式来创建多线程。因为接口可以多实现。事实上,Thread类的源码中也是实现了Runnable接口。
1.2.3实例变量与线程安全
实例变量与线程安全的问题分为共享数据和不共享数据两种情况。
首先,来看看不共享数据的情况:
public class MyThread extends Thread {
private int count = 5;
public MyThread(String name) {
super();
this.setName(name);
}
@Override
public void run() {
super.run();
while(count > 0){
count--;
System.out.println("由"+this.currentThread().getName() + "计算,count = "+count);
}
}
}
public class MyThreadTest {
public static void main(String[] args) {
MyThread a = new MyThread("A");
MyThread b = new MyThread("B");
MyThread c = new MyThread("C");
a.start();
b.start();
c.start();
}
}
从运行结果上看,每个线程都是一个对象,都有自己的count值。各个线程之间计算的也是自己的值,互不干扰。
再来看看,数据共享的情况:
public class MyThread extends Thread {
private int ticket = 10;
@Override
public void run() {
super.run();
ticket--;
System.out.println("由"+this.currentThread().getName()+"计算,ticket = "+ ticket);
}
}
public class MyThreadTest {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread a = new Thread(myThread,"a");
Thread b = new Thread(myThread,"b");
Thread c = new Thread(myThread,"c");
Thread d = new Thread(myThread,"d");
Thread e = new Thread(myThread,"e");
a.start();
b.start();
c.start();
d.start();
e.start();
}
}
新创建的5个线程,都调用MyThread类的run方法,所以共享实例变量。从运行结果上看,程序是异步的。事实上,ticket--是分为三个步骤:
1.读取ticket的值;2.进行自减操作;3.写入ticket的值
所以在这三个步骤中,有多个线程来操作时,一定会出现非线程安全问题。为了解决这个问题,我们使用synchronized关键字修饰run方法。
什么是非线程安全问题呢?就是在多个线程访问实例变量进行操作的时候,出现值更改,不同步的情况,进而影响程序的执行流程。
public class MyThreadSynchronized extends Thread {
private int count = 10;
@Override
synchronized public void run() {
super.run();
count--;
System.out.println("由"+this.currentThread().getName()+"计算,count = "+ count);
}
}
public class MyThreadSynchronizedTest {
public static void main(String[] args) {
MyThreadSynchronized mts = new MyThreadSynchronized();
Thread a = new Thread(mts,"1");
Thread b = new Thread(mts,"2");
Thread c = new Thread(mts,"3");
Thread d = new Thread(mts,"4");
Thread e = new Thread(mts,"5");
a.start();
b.start();
c.start();
d.start();
e.start();
}
}
这时候,我们可以看到多个线程之间同步效果,多个线程按顺序排队的方式进行减1操作。
线程安全问题解决办法:使用同步方法 。即在方法上加入synchronized关键字。
public class LoginServlet {
private static String usernameRef;
private static String passwordRef;
synchronized public static void doPost(String username,String password){
try{
usernameRef = username;
if("a".equals(username)){
Thread.sleep(5000);
}
passwordRef = password;
System.out.println("username = "+ usernameRef + " password = "+ passwordRef );
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public class ALogin extends Thread {
@Override
public void run() {
LoginServlet.doPost("a","aa");
}
}
public class BLogin extends Thread {
@Override
public void run() {
LoginServlet.doPost("b","bb");
}
}
public class LoginServletTest {
public static void main(String[] args) {
ALogin a = new ALogin();
a.start();
BLogin b = new BLogin();
b.start();
}
}
doPost方法不加synchronized关键字
doPost方法加上synchronized关键字
从运行结果上看,不加synchronized关键字的时候,打印结果是由问题的,而加上synchronized关键字后打印结果没有问题。
synchronized关键字相当于给方法上了一个锁,当某个线程拿到这个锁时,该线程会执行run方法里面的内容,而其他线程此时无法执行run方法里面的内容,只有当这个线程执行完毕,释放锁后,其他线程再拿到该锁,才能执行run方法里面的内容。