查漏补缺之——Java多线程
复习面试题中遇到锁的内容当时大一学习的时候感觉懵懂,现在重新复习一下。
1.1多线程
1.1.1线程
1.什么是线程
线程是程序执行的一条路径,一个进程中包含多条进程
2.并行与并发
并行是两个任务同时运行,甲任务进行的同时,乙任务也在进行
并发是指两个任务都请求运行,处理器只能处理一个任务,就将两个任务进行轮流进行
3.Java程序运行原理和JVM的启动是否为多线程
A:Java程序运行原理
Java命令会启动Java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程,该进程为主线程,主线程去调用某个main方法
B:JVM的启动是多线程的吗
JVM的启动至少启动了垃圾回收线程和主线程,所以是多线程的
证明JVM运行是多线程
package com.littlepage.test2; public class Demo { @Override protected void finalize(){ System.out.println("rubbish is cleaned"); } public static void main(String[] args) { for(int i=0;i<1000000;i++){ new Demo(); } for(int i=0;i<10000;i++){ System.out.println("main is doing"); } } }
1.1.1实现方式
1.继承Thread类
public class TreadDemo01 { public static void main(String[] args) { MyThread mt=new MyThread(); mt.start(); for(int i=0;i<1000;i++){ System.out.println("Main method"); } } } class MyThread extends Thread{ @Override public void run() { for(int i=0;i<1000;i++){ System.out.println("My Thread is running"); } } }
2.实现runnable接口
package com.littlepage.test2; public class ThreadDemo02 { public static void main(String[] args) { MyRunnable mr=new MyRunnable(); new Thread(mr).start(); for(int i=0;i<1000;i++){ System.out.println("Main method"); } } } class MyRunnable implements Runnable{ @Override public void run() { for(int i=0;i<1000;i++){ System.out.println("My Runnable is running"); } } }
3.何时使用继承何时使用接口
一般情况下使用继承的多线程,因为代码简单,接口线程在一个类有了父类的时候,使用接口
4.匿名内部类进行多线程
package com.littlepage.test2; public class ThreadDemo03 { public static void main(String[] args) { new Thread(){ @Override public void run() { for(int i=0;i<1000;i++){ System.out.println("My Thread is running"); } } }.start(); for(int i=0;i<1000;i++){ System.out.println("Main method"); } } }
package com.littlepage.test2; public class ThreadDemo03 { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { for(int i=0;i<1000;i++){ System.out.println("My Runnable is running"); } } }).start(); for(int i=0;i<1000;i++){ System.out.println("Main method"); } } }
1.2获取线程名和设置线程名
1.1获取线程名字(线程的默认名字为Thread-0 Thread-1....)
package com.littlepage.test2; public class Demo04GetName { public static void main(String[] args) { new Thread(){ @Override public void run() { System.out.println(getName()); } }.start(); new Thread(){ @Override public void run() { System.out.println(getName()); } }.start(); } }
package com.littlepage.test2; public class Demo04GetName { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }).start();; } }
1.2设置线程名
package com.littlepage.test2; public class Demo04GetName { public static void main(String[] args) { Thread t1=new Thread(){ @Override public void run() { System.out.println(getName()); } }; t1.setName("线程0"); Thread t2=new Thread(){ @Override public void run() { System.out.println(getName()); } }; t2.setName("线程1"); t1.start();t2.start(); } }
1.3休眠线程
1.1简介休眠方法
Thread.sleep(long millis) 休眠millis毫秒
Thread.sleep(long millis,int nanosecond) 休眠millis毫秒和nanosecond纳秒
举例:省略
1.4守护线程(Daemon)
守护线程就是我们所说的后台程序,非守护线程结束,守护线程将会立即结束
举例:
package com.littlepage.test2; public class DemoDaemon { public static void main(String[] args) { Thread a=new Thread(){ @Override public void run() { for(int i=0;i<10000;i++){ System.out.println(getName()+"\t"+i); } } }; Thread b=new Thread(){ @Override public void run() { for(int i=0;i<2;i++){ System.out.println(getName()+"\t"+i); } } }; a.setDaemon(true); a.start(); b.start(); } }
线程2结束,守护线程1也立即结束
1.5加入线程
加入线程join() 当前线程暂停,等待指定的线程执行结束后,再进行执行
举例:
package com.littlepage.test2; public class DemoDaemon { public static void main(String[] args) { final Thread a=new Thread(){ @Override public void run() { for(int i=0;i<10000;i++){ System.out.println(getName()+"\t"+i); } } }; Thread b=new Thread(){ @Override public void run() { for(int i=0;i<2;i++){ if(i==1) try { a.join();//等a线程执行完再执行完,再执行b } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName()+"\t"+i); } } }; a.setDaemon(true); a.start(); b.start(); } }
1.4同步代码块
1.4.1什么情况下需要同步
多线程并发,多段代码同时执行,我们希望某一段代码执行过程中CPU不要切换到其他线程来工作,这时候我们需要同步
代码示例:
package com.littlepage.test2; public class DemoSynchronized { public static void main(String[] args) { final Printer p=new Printer(); new Thread(){ public void run(){ while(true) p.print1(); } }.start(); new Thread(){ public void run(){ while(true) p.print2(); } }.start(); } } class Printer{ public void print1(){ System.out.print("A"); System.out.print("P"); System.out.print("P"); System.out.print("l"); System.out.println("e"); } public void print2(){ System.out.print("B"); System.out.print("a"); System.out.print("n"); System.out.print("a"); System.out.print("n"); System.out.println("a"); } }
这段代码在运行过程中出现了以下情况
原因是,打印过程中进行了切换导致,所以我们需要一个同步锁
package com.littlepage.test2; public class DemoSynchronized { public static void main(String[] args) { final Printer p=new Printer(); new Thread(){ public void run(){ while(true) p.print1(); } }.start(); new Thread(){ public void run(){ while(true) p.print2(); } }.start(); } } class Printer{ Object lock=new Object(); public void print1(){ synchronized (lock) { System.out.print("A"); System.out.print("P"); System.out.print("P"); System.out.print("l"); System.out.println("e"); } } public void print2(){ synchronized (lock) { System.out.print("B"); System.out.print("a"); System.out.print("n"); System.out.print("a"); System.out.print("n"); System.out.println("a"); } } }
锁可以是任何一个对象,这边直接new一个Object,但是在两个方法内部,必须用一个lock
1.5同步方法
package com.littlepage.test2; public class DemoSynchronized { public static void main(String[] args) { final Printer p=new Printer(); new Thread(){ public void run(){ while(true) p.print1(); } }.start(); new Thread(){ public void run(){ while(true) p.print2(); } }.start(); } } class Printer{ public synchronized void print1(){ System.out.print("A"); System.out.print("P"); System.out.print("P"); System.out.print("l"); System.out.println("e"); } public synchronized void print2(){ System.out.print("B"); System.out.print("a"); System.out.print("n"); System.out.print("a"); System.out.print("n"); System.out.println("a"); } }
同步方法的锁是this, 所以我们有个想法, 包括同步代码块和同步代码,我们所有的锁都可以用this关键字, 不用Object造成冗余
静态方法的锁是字节码对象
1.6线程的安全问题
1.6.1书本的售票例子
package com.littlepage.test2; public class DemoTicket { public static void main(String[] args) { new TicketSeller("窗口1").start(); new TicketSeller("窗口2").start(); new TicketSeller("窗口3").start(); new TicketSeller("窗口4").start(); } } class TicketSeller extends Thread{ private static int tickets=100; public TicketSeller(String windowName) { super(windowName); } public void run(){ while(true){ if(tickets<=0){ break; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName()+"......"+tickets--); } } }
结果出现了重复票和负票
解决方式,加上同步代码块
package com.littlepage.test2; public class DemoTicket { public static void main(String[] args) { new TicketSeller("窗口1").start(); new TicketSeller("窗口2").start(); new TicketSeller("窗口3").start(); new TicketSeller("窗口4").start(); } } class TicketSeller extends Thread{ private static int tickets=100; public TicketSeller(String windowName) { super(windowName); } public void run(){ synchronized (TicketSeller.class) { while(true){ if(tickets<=0){ break; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName()+"......"+tickets--); } } } }
使用Runnable实现
1.7死锁