java多线程
此文档为java多线程的学习总结
java多线程
这张图的几点解释:
1.thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
2.sleep()与wait()。
对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
在调用sleep()方法的过程中,线程不会释放对象锁。而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备
获取对象锁进入运行状态。
别人总结的:
是否释放锁:调用sleep和yield的时候不释放当前线程所获得的锁,但是调用await/wait的时候却释放了其获取的锁并阻塞等待。
调用后何时恢复:
sleep让线程阻塞,且在指定的时间之内都不会执行,时间到了之后恢复到就绪状态,也不一定被立即调度执行;
yield只是让当前对象回到就绪状态,还是有可能马上被再次被调用执行。
await/wait,它会一直阻塞在条件队列之上,之后某个线程调用对应的notify/signal方法,才会使得await/wait的线程回到就绪状态,也是不一定立即执行。
谁的方法:yield和sleep方法都是Thread类的,而wait方法是Object类的,await方法是Condition显示条件队列的。
执行环境:yield和sleep方法可以放在线程中的任意位置,而await/wait方法必须放在同步块里面,否则会产生运行时异常。
1.多线程中线程安全:线程同步与锁定synchronized
/**
* FileName: WebDemo
* Author: braincao
* Date: 2018/10/2 10:59
* Description: 模拟抢票过程,主要演示线程不安全与加锁后安全
*/
public class WebDemo implements Runnable{
private int num = 20; //总共20张票
private boolean flag = true;
public static void main(String[] args){
//真实角色
WebDemo w = new WebDemo();
//代理角色
Thread t1 = new Thread(w, "路人");
Thread t2 = new Thread(w, "黄牛");
Thread t3 = new Thread(w, "程序员");
//启动线程
t1.start();
t2.start();
t3.start();
}
public void run(){
while(flag) {
// test01(); //线程不安全
// test02(); //线程安全
test03(); //线程安全
}
}
//线程不安全
public void test01(){
if(num<=0){
flag = false;
return;
}
try{
Thread.sleep(500); //模拟抢票延时
}
catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了" + num--);
}
//线程安全,synchronized同步方法
public synchronized void test02(){
if(num<=0){
flag = false;
return;
}
try{
Thread.sleep(100); //模拟抢票延时
}
catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了" + num--);
}
//线程安全,synchronized同步块
public void test03(){
synchronized (this){
if(num<=0){
flag = false;
return;
}
try{
Thread.sleep(100); //模拟抢票延时
}
catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了" + num--);
}
}
}
out:
//线程不安全test01
黄牛抢到了20
路人抢到了19
程序员抢到了18
路人抢到了17
程序员抢到了15
黄牛抢到了16
路人抢到了14
程序员抢到了13
黄牛抢到了12
黄牛抢到了11
路人抢到了9
程序员抢到了10
路人抢到了8-->重复
程序员抢到了7
黄牛抢到了8-->重复
路人抢到了5
黄牛抢到了4
程序员抢到了6
黄牛抢到了3
路人抢到了1
程序员抢到了2
//线程安全test02、test03
路人抢到了20
路人抢到了19
路人抢到了18
路人抢到了17
程序员抢到了16
程序员抢到了15
程序员抢到了14
程序员抢到了13
程序员抢到了12
程序员抢到了11
程序员抢到了10
程序员抢到了9
程序员抢到了8
程序员抢到了7
程序员抢到了6
程序员抢到了5
程序员抢到了4
程序员抢到了3
程序员抢到了2
程序员抢到了1
###2、多线程中的单例模式(加同步锁)
- 单例模式创建的方式
1.懒汉式:
1)、构造器私有化,避免外部直接创建对象
2)、声明一个私有的静态变量
3)、创建一个对外的公共的静态方法访问该变量,如果变量没有对象,创建该对象。且考虑多线程因素,这里要使用double checking的同步锁
懒汉式单例模式模板:
/**
* FileName: Lanhan
* Author: braincao
* Date: 2018/10/2 15:44
* Description: 单例模式之懒汉式
* 1、构造器私有化,避免外部直接创建对象
* 2、声明一个私有的静态变量
* 3、创建一个对外的公共的静态方法访问该变量,如果变量没有对象,创建该对象。且考虑多线程因素,这里要使用double checking的同步锁
*/
public class Lanhan {
private static Lanhan instance = null;
private Lanhan(){
}
public static Lanhan getInstance(){
if(Lanhan==null){ //double checking 这里是提高效率作用
synchronized(Lanhan.class){ //同步块synchronized()中的参数是引用类型,但这里没有this对象,注意要用Lanhan.class
if(Lanhan==null){//double checking 这里是安全同步作用
instance = new Lanhan();
}
}
}
return instance;
}
}
饿汉式单例模式模板:
public class Lanhan {
private static class JvmHolder{ //内部类,调用时再创建对象,而不是加载类时就创建对象,更有效率
private static Lanhan instance = new Lanhan();
}
private Lanhan(){
}
public static Lanhan getInstance(){
return JvmHolder.instance;
}
}
一个测试例子:
/**
* 单例设计模式:确保一个类只有一个对象
* 多线程中单例模式需要添加同步锁
*/
class WebDemo{
public static void main(String[] args){
// Jvm jvm1 = Jvm.getInstance();
// Jvm jvm2 = Jvm.getInstance();
// System.out.println(jvm1);
// System.out.println(jvm2); //单线程中,两者相等
JvmThread t1 = new JvmThread(100);
JvmThread t2 = new JvmThread(500);
t1.start();
t2.start();
//out1:Thread-0-->创建:Jvm@46543a6c Thread-1-->创建:Jvm@59dafc71 //多线程中,t1,t2不相等。这时需要添加同步锁
//out2:Thread-0-->创建:Jvm@6ab31cd3 Thread-1-->创建:Jvm@6ab31cd3 //加同步锁后,多线程中t1、t2相等
}
}
//多线程
class JvmThread extends Thread{
private long time;
public JvmThread(){
}
public JvmThread(long time){
this.time = time;
}
@Override
public void run(){
System.out.println(Thread.currentThread().getName() + "-->创建:" + Jvm.getInstance(time));
}
}
/**
* 懒汉式
* 1、构造器私有化,避免外部直接创建对象
* 2、声明一个私有的静态变量
* 3、创建一个对外的公共的静态方法访问该变量,如果变量没有对象,创建该对象
*/
class Jvm{
private static Jvm instance = null; //懒汉式就是这里懒的创建对象
private Jvm(){
}
//在这里添加同步锁后,多线程模式下也一样是单例模式了
public static Lanhan getInstance(){
if(Lanhan==null){ //double checking 这里是提高效率作用
synchronized(Lanhan.class){ //同步块synchronized()中的参数是引用类型,但这里没有this对象,注意要用Lanhan.class
if(Lanhan==null){//double checking 这里是安全同步作用
instance = new Lanhan();
}
}
}
return instance;
}
}
3、死锁
过多的同步容易造成死锁,解决方法:生产者消费者模式
死锁例子演示:
public class Demo implements Runnable{
private boolean flag = false;
private static Object o1 = new Object();
private static Object o2 = new Object();
public Demo(boolean flag){
this.flag = flag;
}
public void run(){
if(flag==true){
synchronized (o1){
System.out.println("t1 : o1");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2){
System.out.println("t2 : o1");
}
}
}
else{
synchronized (o2){
System.out.println("t2 : o2");
synchronized (o1){
System.out.println("t2 : o2");
}
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new Demo(true));
Thread t2 = new Thread(new Demo(false));
t1.start();
t2.start();
//out:
//t1:o1
//t2:o2...
}
}
4、生产者消费者模式:信号灯法
wait():等待、释放锁;sleep():等待、不释放锁
notify()/notifyAll():唤醒
wait()与notify()/notifyAll()是要与锁synchronized一起使用的
/**
* FileName: App
* Author: braincao
* Date: 2018/10/2 16:13
* Description:生产者消费者模式:信号灯法
*/
public class App{
public static void main(String[] args){
//共同资源
Movie m = new Movie();
//两个线程
Player p = new Player(m);
Watcher w = new Watcher(m);
new Thread(p).start();
new Thread(w).start();
}
}
/**
* 一个场景,共同资源
* 且使用生产者消费者模式:信号灯法
* wait():等待、释放锁;sleep():等待、不释放锁
* notify()/notifyAll():唤醒
* wait()与notify()/notifyAll()是要与锁synchronized一起使用的
*
*/
class Movie {
private String pic;
private boolean flag;
/**
* 信号灯
* flag-->T 生产者生产,消费者等待,生产完成后通知消费者
* flag-->F 消费者消费,生产者等待,消费完成后通知生产者
*/
//生产
public synchronized void player(String pic){
if(!flag){//生产者等待
try{
this.wait(); //等待,释放锁
}catch (InterruptedException e){
e.printStackTrace();
}
}
//开始生产
try{
Thread.sleep(500);
}catch (InterruptedException e){
e.printStackTrace();
}
//生产完毕
System.out.println("生产了:" + pic);
this.pic = pic;
//通知消费者,唤醒正在等待的线程,这里只有一个线程在等待notifyAll()也可以
this.notify();
//生产者停下
this.flag = false;
}
//观看
public synchronized void watch(){
if(flag){//消费者等待
try{
this.wait(); //等待,释放锁
}catch (InterruptedException e){
e.printStackTrace();
}
}
//开始消费
try{
Thread.sleep(200);
}catch (InterruptedException e){
e.printStackTrace();
}
//消费完毕
System.out.println("消费了:" + pic);
//通知生产者,唤醒正在等待的线程,这里只有一个线程在等待notifyAll()也可以
this.notify();
//消费者停下
this.flag = true;
}
}
/**
* 生产者
*/
class Player implements Runnable{
private Movie m;
public Player(Movie m){
this.m = m;
}
public void run(){
for(int i=0; i<20; ++i){
if(i%2==0){
m.player("左青龙");
}
else{
m.player("右白虎");
}
}
}
}
/**
* 消费者
*/
class Watcher implements Runnable{
private Movie m;
public Watcher(Movie m){
this.m = m;
}
public void run(){
for(int i=0; i<20; ++i){
m.watch();
}
}
}
out:
消费了:null
生产了:左青龙
消费了:左青龙
生产了:右白虎
消费了:右白虎
生产了:左青龙
消费了:左青龙
生产了:右白虎
消费了:右白虎
生产了:左青龙
消费了:左青龙
生产了:右白虎
消费了:右白虎
生产了:左青龙
5、线程之任务调度
想让该程序晚上11点跑,使用线程之任务调度,Timer定时器类,相当于一个闹钟。
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
class TimerDemo {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("so easy...");
}
}, new Date(System.currentTimeMillis()+3000), 1000); //3秒后运行,每隔1秒运行一次; 如果没有period参数,就只运行一次
}
}
out:
so easy...
so easy...
so easy...
so easy...
欢迎转载,欢迎错误指正与技术交流,欢迎交友谈心
文章标题:java多线程
文章字数:2.7k
本文作者:Brain Cao
发布时间:2018-12-16, 15:54:20
最后更新:2020-03-06, 23:25:51
原始链接:https://braincao.cn/2018/12/16/java-multi-thread/版权声明:本文为博主原创文章,遵循 BY-NC-SA 4.0 版权协议,转载请保留原文链接与作者。