java多线程
一、进程和线程
进程是一个运行中的程序,是系统进行调度和分配资源的一个单位。当程序进入内存中运行时,就是一个进程。如windows系统同时运行360和QQ,这就是两个进程。
线程可以理解为进程的多条执行路径,是操作系统调度的基本单位,它是比进程更小的能独立运行的基本单位。一个进程至少包含一个线程,一个线程至少属于一个进程。线程负责了代码的执行。
二、并发与并行
并发是一次处理几个任务,这里的一次是指通过CPU调度,多个任务在同个时间内看似都在执行,这里针对的是同一个CPU执行任务。
并行是同一时刻处理几个任务,至少需要两个CPU才能实现同一时刻处理几个任务。
三、java如何创建线程
创建线程的几种方式:1、继承Thread类,其实Thread了也实现了Runnable接口;2、实现Runnable接口(推荐);3、实现Callable接口,其实原理也是和Runnable有关。
public class Test { public static void main(String[] args) throws InterruptedException { Thread t1 = new MyThread1(); t1.start(); Runnable r = new MyThread2(); Thread t2 = new Thread(r); t2.start(); Callable<Object> c = new MyThread3(); FutureTask<Object> future = new FutureTask<Object>(c);//其实这个类就是一个Runnable的实现类 Thread t3 = new Thread(future); t3.start(); } } class MyThread1 extends Thread{ @Override public void run() { super.run(); for(int i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()+" "+i); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } class MyThread2 implements Runnable{ @Override public void run() { for(int i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()+" "+i); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } class MyThread3 implements Callable<Object>{ @Override public Object call() throws Exception { for(int i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()+" "+i); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } return null; } }
运行结果:
Thread-0 0
Thread-1 0
Thread-2 0
Thread-0 1
Thread-2 1
Thread-1 1
Thread-0 2
Thread-1 2
Thread-2 2
Thread-0 3
Thread-2 3
Thread-1 3
Thread-0 4
Thread-2 4
Thread-1 4
Thread-0 5
Thread-2 5
Thread-1 5
Thread-0 6
Thread-2 6
Thread-1 6
Thread-0 7
Thread-1 7
Thread-2 7
Thread-0 8
Thread-2 8
Thread-1 8
Thread-0 9
Thread-1 9
Thread-2 9
四、线程的状态(借用别人的一张图)
1、初始状态(new):new Thread() 创建一个线程的时候,此时线程处于初始状态,线程还没有运行。
2、可运行状态(Runnable):线程调用了start()方法之后,就会进入可运行状态,等待被调度选中获取cpu的执行权。
3、运行中(Running):线程被调度选中之后,获得cpu的执行权,执行代码。运行中的线程也可能因为执行了Thread.yield()方法或者时间片用完,重新进入可运行状态
4、阻塞状态(blocked):
①等待阻塞:线程运行到o.wait()时,当前线程会释放锁,然后jvm会将线程放入等待池中。
②同步阻塞:运行中的线程获取锁时,如果锁被其他线程占用,则会把该线程放入等锁池中。
③其他阻塞:运行中的线程调用了Thread.sleep()方法和t.join()方法或者进行I/O,jvm会将线程设置为阻塞状态。
当Thread.sleep()超时、t线程执行完毕或者I/O结束,线程又会进入可运行状态。
五、 线程安全和同步
如果多个线程同时运行一段代码得到的结果总是和单线程执行时的结果一样,这样代码就是线程安全的,否则线程不安全。
线程安全问题产生的原因:
1、是否具有多线程
2、是否具有共享数据
3、是否有多条语句操作共享数据
如下代码就是线程不安全的:
public class Test2{ public static void main(String[] args) { Runnable r1 = new SellTicket("窗口1"); Runnable r2 = new SellTicket("窗口2"); Thread t1 = new Thread(r1); Thread t2 = new Thread(r2); t1.start(); t2.start(); } } class SellTicket implements Runnable{ private static int ticketNum = 10; private String name; SellTicket(String name){ this.name = name; } @Override public void run() { while(ticketNum > 0){ try { if(ticketNum > 0 ){ ticketNum -= 1; System.out.println(name+"卖出一张票剩余票数为:"+ticketNum); Thread.sleep(100); } } catch (InterruptedException e) { e.printStackTrace(); } } } }
执行结果:
窗口2卖出一张票剩余票数为:8
窗口1卖出一张票剩余票数为:8
窗口2卖出一张票剩余票数为:6
窗口1卖出一张票剩余票数为:6
窗口1卖出一张票剩余票数为:5
窗口2卖出一张票剩余票数为:5
窗口1卖出一张票剩余票数为:4
窗口2卖出一张票剩余票数为:3
窗口2卖出一张票剩余票数为:2
窗口1卖出一张票剩余票数为:2
窗口1卖出一张票剩余票数为:1
窗口2卖出一张票剩余票数为:1
窗口2卖出一张票剩余票数为:0
窗口1卖出一张票剩余票数为:0
线程安全问题解决办法:
1、使用synchronized对代码进行同步
①synchronized可以用来同步代码块和方法,并且需要结合锁对象来使用
②当synchronized同步代码块时,需要手动指定锁对象;当synchronized同步非静态方法时,锁对象是类的一个实例对象;当synchronized同步静态方法时,锁对象是整个类。
③锁对象是互斥的,当一个线程获得了该锁之后,其他线程想要获得该锁对象,必须要等待该线程释放锁。当一个线程获取锁后,其可以再次获取该锁,这就是锁重入。
④使用synchronized进行同步时,必须要保证所有线程获得了同一个锁,不然是无法进行同步的,会出现线程安全问题。
public class Test3 { public static void main(String[] args) { Runnable r1 = new SellTicket("窗口1"); Runnable r2 = new SellTicket("窗口2"); Thread t1 = new Thread(r1); Thread t2 = new Thread(r2); t1.start(); t2.start(); } } class SellTicket implements Runnable{ private static int ticketNum = 10; private static Object lock = new Object(); private String name; SellTicket(String name){ this.name = name; } @Override public void run() { while(ticketNum > 0){ synchronized(lock){ try { if(ticketNum > 0 ){ ticketNum -= 1; System.out.println(name+"卖出一张票剩余票数为:"+ticketNum); Thread.sleep(100); } } catch (InterruptedException e) { e.printStackTrace(); } } } } } 执行结果 窗口1卖出一张票剩余票数为:9 窗口1卖出一张票剩余票数为:8 窗口2卖出一张票剩余票数为:7 窗口2卖出一张票剩余票数为:6 窗口2卖出一张票剩余票数为:5 窗口1卖出一张票剩余票数为:4 窗口1卖出一张票剩余票数为:3 窗口2卖出一张票剩余票数为:2 窗口2卖出一张票剩余票数为:1 窗口1卖出一张票剩余票数为:0
2、使用Lock对代码进行同步
在jdk5新加入了Lock类来对代码进行同步,它比synchronized功能更加丰富。Lock类需要手动释放锁,并且必须要在finally里面释放。
public class Test4 { public static void main(String[] args) { Runnable r1 = new SellTicket("窗口1"); Runnable r2 = new SellTicket("窗口2"); Thread t1 = new Thread(r1); Thread t2 = new Thread(r2); t1.start(); t2.start(); } } class SellTicket implements Runnable{ private static int ticketNum = 10; private static Lock lock = new ReentrantLock(); private String name; SellTicket(String name){ this.name = name; } @Override public void run() { while(ticketNum > 0){ lock.lock(); try { if(ticketNum > 0 ){ ticketNum -= 1; System.out.println(name+"卖出一张票剩余票数为:"+ticketNum); Thread.sleep(100); } } catch (InterruptedException e) { e.printStackTrace(); }finally{ lock.unlock(); } } } }
执行结果:
窗口1卖出一张票剩余票数为:9
窗口1卖出一张票剩余票数为:8
窗口1卖出一张票剩余票数为:7
窗口1卖出一张票剩余票数为:6
窗口1卖出一张票剩余票数为:5
窗口1卖出一张票剩余票数为:4
窗口1卖出一张票剩余票数为:3
窗口1卖出一张票剩余票数为:2
窗口2卖出一张票剩余票数为:1
窗口2卖出一张票剩余票数为:0
六、线程的死锁
线程死锁是由于线程设计的缺陷,在执行过程多个线程都在等待对方获取的锁,导致都无法继续进行下去。
出现线程死锁的原因:
多个线程在执行的过程中,线程获取了锁之后,都需要获取对方的锁才能继续执行下去,这样互补退让,导致死锁。
public class Test5 { public static void main(String[] args) { Runnable r1 = new MyThread4("锁1","锁2"); Runnable r2 = new MyThread4("锁2","锁1"); Thread t1 = new Thread(r1); Thread t2 = new Thread(r2); t1.start(); t2.start(); } } class MyThread4 implements Runnable{ private String lock1; private String lock2; MyThread4(String lock1,String lock2){ this.lock1 = lock1; this.lock2 = lock2; } @Override public void run() { synchronized(lock1){ System.out.println("获取了"+lock1+",等待"+lock2); synchronized(lock2){ System.out.println("获取了两个锁,执行成功"); } } } } 执行结果: 获取了锁2,等待锁1 获取了锁1,等待锁2
七、生产者与消费者
生产者和消费者就是通过线程间的通信来达到平衡生产和消费之间的关系。
public class Test6 { public static void main(String[] args) { Object lock = new Object(); List<String> list = new LinkedList<String>(); Runnable producer = new Producer(list,lock); Runnable consumer = new Consumer(list,lock); Thread t1 = new Thread(producer); Thread t2 = new Thread(consumer); t1.start(); t2.start(); } } class Producer implements Runnable{ private Object lock; private List<String> list; Producer(List<String> list,Object lock){ this.lock = lock; this.list = list; } @Override public void run() { while(true){ synchronized (lock) { if(list.size() > 0){ try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else{ list.add(""); lock.notify(); System.out.println("生产一个现在总数是"+list.size()); } } } } } class Consumer implements Runnable{ private Object lock; private List<String> list; Consumer(List<String> list,Object lock){ this.lock = lock; this.list = list; } @Override public void run() { while(true){ synchronized (lock) { if(list.size() <= 0){ try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else{ list.remove(""); lock.notify(); System.out.println("消费一个现在总数是"+list.size()); } } } } }
执行结果:
生产一个现在总数是1
消费一个现在总数是0
生产一个现在总数是1
消费一个现在总数是0
生产一个现在总数是1
消费一个现在总数是0
...