多线程
* 1.什么是线程
* 线程是程序执行的一条路径, 一个进程中可以包含多条线程
* 多线程并发执行可以提高程序的效率, 可以同时完成多项工作
* 2.多线程的应用场景
* QQ同时和多个人一起视频
* 服务器同时处理多个客户端请求
多线程并行和并发的区别
* 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)
* 并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。
* 比如我跟两个网友聊天,左手操作一个电脑跟甲聊,同时右手用另一台电脑跟乙聊天,这就叫并行。
* 如果用一台电脑我先给甲发个消息,然后立刻再给乙发消息,然后再跟甲聊,再跟乙聊。这就叫并发。
1、 多线程程序实现
1.1、 Thread类实现多线程
* 多线程的实现方式一(首选)
* 1、自定义类继承Thread类
* 2、在自定义类中重写run方法 将要执行的代码写在里面
* 3、主方法中实例化自定义类对象
* 4、调用start();方法开启多线程
public class Demo01_Thread {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
for(int i = 0;i < 1000;i++)
System.out.println("0000000000000000000");
}
}
class MyThread extends Thread{
public void run() {
for(int i = 0;i < 1000;i++)
System.out.println("---------------------");
}
}
1.2、 Runnable 接口实现多线程
* 多线程的实现方式二
* 1、自定义类 实现 Runnable 接口
* 2、在自定义类中重写run方法 将要执行的代码写在里面
* 3、主方法中实例化自定义类对象
* 4、创建线程对象Thread,将自定义类对象当作参数传给Thread
* 5、调用start();方法开启多线程
public class Demo02_Runnable {
public static void main(String[] args) {
// TODO Auto-generated method stub
MyRunnable mt = new MyRunnable();
Thread t = new Thread(mt);
t.start();
for(int i = 0;i < 1000;i++)
System.out.println("66666666666666666666");
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
for( int i = 0;i < 1000;i++ )
System.out.println("-----------------------");
}
}
1.3、 匿名内部类实现线程的两种方式
public class Demo03 {
public static void main(String[] args) {
new Thread() { //1、匿名类继承Thread方法
public void run() {
//2、重写run方法
}
}.start();//3、调用start方法
/** ****************************************/
new Thread(new Runnable() {
public void run() {
}
}).start();
}
}
2、 多线程 获取名字和设置名字
2.1、 Thread类实现获取名字和设置名字
public static void main(String[] args) {
// TODO Auto-generated method stub
//父类引用指向子类对象
Thread t1 = new Thread() {
public void run() {
for( int i = 0;i < 1000;i++ )
System.out.println(this.getName() + "....aaaaa"); //获取该线程的名字
}
};
Thread t2 = new Thread() {
public void run() {
for( int i = 0;i < 1000;i++ )
System.out.println(this.getName() + "....111111"); //获取该线程的名字
}
};
t1.setName("第一个线程的名字"); //或者在Thread("第一个线程的名字")效果一样
t2.setName("第二个线程的名字");
t1.start();
t2.start();
}
2.2、 Runnabel接口 实现获取名字和设置名字
//cuurentThread()方法 返回当前正在执行的线程对象的引用 返回类型Thread
public static void main(String[] args) {
// TODO Auto-generated method stub
//new Thread(Runnable的子类对象).start();
Thread t1 = new Thread(new Runnable() {
//本类引用指向本类对象
public void run() {
for( int i = 0;i < 1000;i++ )
System.out.println(Thread.currentThread().getName() + "....aaaaa"); //获取该线程的名字
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
for( int i = 0;i < 1000;i++ )
System.out.println(Thread.currentThread().getName() + "....000000"); //获取该线程的名字
}
});
t1.setName("第一个线程");
t2.setName("第二个线程");
t1.start();
t2.start();
}
3、 休眠线程
Thread.sleep(1000); //停留一秒
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
for(int i = 1;i < 20;i++) {
Thread.sleep(1000);//停留一秒
System.out.println("这是第" + i + "秒");
}
}
4、 守护线程
守护线程:设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出
t1.setDaemon(true); //设置t1为守护线程,则t2线程执行完成后 t1自动结束
public static void main(String[] args) {
Thread t1 = new Thread() {
public void run() {
for(int i = 0;i < 1000;i++ )
System.out.println(getName() + "....aaaa");
}
};
Thread t2 = new Thread() {
public void run() {
for(int i = 0;i < 2;i++)
System.out.println(getName() + "....1111");
}
};
t1.setDaemon(true); //设置t1为守护线程,则t2线程执行完成后 t1自动结束
t1.start();
t2.start();
}
5、 加入线程(插队线程)
* join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
* join(int), 可以等待指定的毫秒之后继续
public static void main(String[] args) {
// TODO Auto-generated method stub
final Thread t1 = new Thread() {
public void run() {
for(int i = 0;i < 100;i++ )
System.out.println(getName() + "....aaaa");
}
};
Thread t2 = new Thread() {
public void run() {
//父类方法没有抛异常 则子类方法也不能抛异常 只能在子类方法里面处理异常
for(int i = 0;i < 100;i++) {
if( i==2 ) {
//当i等于二时 则t2线程暂停执行,等t1线程全部执行完成后才可以继续执行t2线程,相当于提升了t2线程的级别
try {
t1.join(); //匿名内部类在使用当前方法的局部变量时 必须用final修饰
// t1.join(30); 当i等于二时 则t2线程暂停执行30ms 之后与t1交替执行
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(getName() + "....1111");
}
}
};
t1.setDaemon(true); //设置t1为守护线程,则t2线程执行完成后 t1自动结束
t1.start();
t2.start();
}
5、 同步代码块 重要
* 1.什么情况下需要同步
当多线程并发(交替进行), 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.
* 2.同步代码块
* 使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块(任意对象都可以当做锁对象,但是匿名对象不可以当做锁对象)
* 多个同步代码块如果使用相同的锁对象, 那么他们就是同步的
/*
* 同步代码块
* 当多线程并发(交替进行), 有多段代码同时执行时,
* 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作
* 这时就需要同步
*/
public class Demo09_synchronized {
public static void main(String[] args) {
// TODO Auto-generated method stub
Printer p = new Printer();
new Thread() {
public void run() {
for(int i = 0;i < 1000;i++)
p.print1();
}
}.start();
new Thread() {
public void run() {
for(int i = 0;i < 1000;i++)
p.print2();
}
}.start();
}
}
class Printer{
Object obj = new Object();
public void print1() {
synchronized(obj) {
/*
* 相当于锁,只有当下面代码全部执行完成 其它线程的代码
* 才可以继续执行
* 任意对象都可以当做锁对象,但是匿名对象不可以当做锁对象
* 被锁的代码需要保证是同一把锁
*/
System.out.print("J");
System.out.print("A");
System.out.print("V");
System.out.print("A");
System.out.print("\r\n");
}
}
public void print2() {
synchronized(obj) {
System.out.print("C");
System.out.print("+");
System.out.print("+");
System.out.print("\r\n");
}
}
}
6、 同步方法 重要
使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的
/*
* 同步方法
* 使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的
* 同步非静态方法默认锁为this
* 同步静态方法默认锁为 该类的字节码对象(Printer.class)
*/
public class Demo10_synchronizeed_2 {
public static void main(String[] args) {
Print p = new Print();
new Thread() {
public void run() {
for(int i = 0;i < 1000;i++)
p.demo01();
}
}.start();
new Thread() {
public void run() {
for(int i = 0;i < 1000;i++)
p.demo02();
}
}.start();
}
}
class Print{
public synchronized void demo01() {
//同步非静态方法默认锁为this
System.out.print("J");
System.out.print("A");
System.out.print("V");
System.out.print("A");
System.out.print("\r\n");
}
public synchronized void demo02() {
System.out.print("C");
System.out.print("+");
System.out.print("+");
System.out.print("\r\n");
}
}
7、线程安全问题
* 多线程并发操作同一数据时, 就有可能出现线程安全问题
* 使用同步技术可以解决这种问题, 把操作数据的代码进行同步, 不要多个线程一起操作
例子:火车售票 4个窗口卖100张票 Thread类实现
/*
* 线程安全问题
* 模拟铁路售票,4个窗口卖100张
* Thread类实现
*/
public class Demo11_Ticket {
public static void main(String[] args) {
// TODO Auto-generated method stub
new Ticket("1号窗口").start(); //会报错 需重写Ticket类构造方法
new Ticket("2号窗口").start();
new Ticket("3号窗口").start();
new Ticket("4号窗口").start();
}
}
class Ticket extends Thread{
public Ticket(String name) {
super(name);
}
private static int T = 100; //100张票 需要定义为static类型才可以使得4个对象共用100这个数
public void run() {
while(true) {
synchronized(Ticket.class) {
//需要加锁,如果不加会出现负票
//此时锁的对象不能是this 因为有四个不同的对象 只能用该类的字节码文件
//(不加锁线程1234睡,中途线程1醒,T--后小于0。 此时线程2醒T还会继续--)
if(T <= 0)
break;
try {
Thread.sleep(10); //线程1 2 3 4睡
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("这是第" + getName() + "....." + T-- + "张票");
}
}
}
}
例子:火车售票 4个窗口卖100张票 Runnable 接口实现
/*
* 线程安全问题
* 模拟铁路售票,4个窗口卖100张
* Runnable 接口实现
*/
public class Demo12_TicketRunnable {
public static void main(String[] args) {
// TODO Auto-generated method stub
TicketR t = new TicketR();
//第一种方式开启四条线程
// Thread t1 = new Thread(t,"1 号窗口");
// Thread t2 = new Thread(t,"2 号窗口");
// Thread t3 = new Thread(t,"3 号窗口");
// Thread t4 = new Thread(t,"4 号窗口");
//
// t1.start();
// //t1.start(); 多次启动一条线程是非法的
// t2.start();
// t3.start();
// t4.start();
// 第二种方式开启四条线程
new Thread(t,"窗口1").start();
new Thread(t,"窗口2").start();
new Thread(t,"窗口3").start();
new Thread(t,"窗口4").start();
}
}
class TicketR implements Runnable{
private int T = 1000;
public void run() {
while(true) {
synchronized(TicketR.class) {
if(T <= 0)
break;
try {
Thread.sleep(10);
}catch(Exception e) {
e.printStackTrace();
}
System.out.println("这是第" + Thread.currentThread().getName() +"....." + T-- + "张票");
}
}
}
}
* 多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁。尽量不要嵌套使用
8、礼让线程 (了解 支持的不好)
一个线程在执行过程中满足某个条件调用Thread.yeield() 则该线程等待其他线程开始 yield让出cpu
/*
* 礼让线程 (了解 支持的不好)
* yieid()
*/
public class Demo01_Yieid {
public static void main(String[] args) {
// TODO Auto-generated method stub
new MyThread().start(); //默认为Thread-0
new MyThread().start(); //Thread-1
}
}
class MyThread extends Thread {
public void run() {
for(int i = 0;i <1000;i++) {
if( i == 100)
Thread.yield();
System.out.println(getName() + "........" + i);
}
}
}
9、设置线程的优先级
setPriority() 设置线程优先级 ,最高级是10 (MAX_PRIORITY), 最低级是1 MIN_PRIORITY。默认是5
public class Demo02_Priority {
public static void main(String[] args) {
// TODO Auto-generated method stub
Thread t1 = new Thread() {
public void run() {
for( int i = 0;i < 1000;i++ )
System.out.println(getName() + "........a");
}
};
Thread t2 = new Thread() {
public void run() {
for( int i = 0;i < 1000;i++ )
System.out.println(getName() + "........bbbbbbbb");
}
};
t1.setPriority(Thread.MAX_PRIORITY); //设置t1为最优先级
t2.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
}
}
10、单例设计模式
* 单例设计模式:保证类在内存中只有一个对象。
* 饿汉式 懒汉式区别:
* 如果都是单线程 饿汉式:空间换时间 懒汉式:时间换空间
* 如果都是多线程 饿汉式不会出现安全隐患,懒汉式会出现多个对象的安全隐患如何保证类在内存中只有一个对象呢?
* (1)控制类的创建,不让其他类来创建本类的对象。private构造函数
* (2)在本类中定义一个本类的对象。Singleton s;
* (3)提供公共的访问方式。 public static Singleton getInstance(){return s}
public class Demo03_Singleton {
public static void main(String[] args) {
// TODO Auto-generated method stub
Singleton s1 = Singleton.getInstance(); //s1和s指向的是一个对象,它俩地址是一样的
Singleton s2 = Singleton.getInstance(); //s2和s指向的也是一个对象
System.out.println(s1 == s2); //true
}
}
// 饿汉式 (先创建对象)
//class Singleton{
// //1 、 私有构造 不让其他类创建本类对象
// private Singleton() {}
// //2 、 本类创建本类对象
// private static Singleton s = new Singleton();
// //3、 对外提供公共的访问方法,返回本类对象
// public static Singleton getInstance() {
// return s;
// }
//
//}
// 懒汉式,单例的延迟加载 (什么时候用什么时候创建对象)
class Singleton{
private Singleton() {}
private static Singleton s;
public static Singleton getInstance() {
if (s==null)
s = new Singleton();
return s;
}
}
11、Runtime类 Timer类
Runtime类是一个单例类 Timer类:计时器
public class Demo04_Runtime {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
//Runtime r = new Runtime(); 错误写法,因为构造方法已经被私有化
Runtime r = Runtime.getRuntime(); //获取到Runtime类的实例化对象
r.exec("要执行的cmd命令");
}
}
Timer类在指定时间执行指定任务
public class Demo05_Timer {
public static void main(String[] args) {
//创建计时器对象
Timer t = new Timer();
//安排指定时间执行MyTimerTast类下的Run方法下指定任务
t.schedule(new MyTimerTast(), 2000);
//2s后输出背单词
}
}
class MyTimerTast extends TimerTask{
public void run() {
//重写run方法
System.out.println("背单词");
}
}
12、两个线程间的通信 (掌握)
1.什么时候需要通信
* 多个线程并发执行时, 在默认情况下CPU是随机切换线程的
* 如果我们希望他们有规律的执行, 就可以使用通信, 例如每个线程执行一次打印
* 2.怎么通信
* 如果希望线程等待, 就调用wait()
* 如果希望唤醒等待的线程, 就调用notify(); notify()方法是随机唤醒一个线程
* 这两个方法必须在同步代码中执行, 并且使用同步锁对象来调用
public class Demo06_Notify {
public static void main(String[] args) {
Printer p = new Printer();
//创建第一条线程
new Thread() {
public void run() {
while(true) {
try {
p.print1();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
//创建第二条线程
new Thread() {
public void run() {
while(true) {
try {
p.print2();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
}
}
class Printer{
private int num = 1;
public void print1() throws InterruptedException {
synchronized(this) {
if( num!=1 )
this.wait();
System.out.print("J");
System.out.print("A");
System.out.print("V");
System.out.print("A");
System.out.print("\r\n");
num = 2;
this.notify();
}
}
public void print2() throws InterruptedException {
synchronized(this) {
if( num!=2 )
this.wait(); //如果没有notify解锁此线程将一直等待
System.out.print("C");
System.out.print("+");
System.out.print("+");
System.out.print("\r\n");
num = 1;
this.notify();
}
}
}
13、多线程互斥锁(n个线程间的通信)
* 互斥锁
* 1、同步
* 使用ReentrantLock类的lock()和unlock()方法进行同步
* 2、通信
* 使用ReentrantLock类的newCondition()方法可以获取Condition对象
* 需要等待的时候使用Condition的await()方法,唤醒时候用signal()方法
* 不同的线程使用不同的Condition,这样就能区分唤醒的是哪个线程了
eg:3个线程间的通信
public class Demo07_ReentrantLock {
public static void main(String[] args) {
Print p = new Print();
//创建三个线程 分别调用子类的三个方法
new Thread() {
public void run() {
try {
while(true) {
p.print1();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}.start();
new Thread() {
public void run() {
try {
while(true) {
p.print2();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}.start();
new Thread() {
public void run() {
try {
while(true) {
p.print3();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}.start();
}
}
class Print{
ReentrantLock r = new ReentrantLock(); //创建ReentrantLock类
//获取Condition对象 因为有不同的线程需要使用不同的Condition 所以需要创建三个对象
Condition c1 = r.newCondition();
Condition c2 = r.newCondition();
Condition c3 = r.newCondition();
private int num = 1;
public void print1() throws InterruptedException {
r.lock(); //互斥锁
while(num != 1) {
c1.await(); //c1等待
}
System.out.print("J");
System.out.print("A");
System.out.print("V");
System.out.print("A");
System.out.print("\r\n");
num = 2;
c2.signal(); //把c2叫醒
r.unlock(); //解锁
}
public void print2() throws InterruptedException {
r.lock(); //互斥锁
while(num != 2) {
c2.await(); //c2等待
}
System.out.print("C");
System.out.print("+");
System.out.print("+");
System.out.print("\r\n");
num = 3;
c3.signal(); //把c3叫醒
r.unlock();
}
public void print3() throws InterruptedException {
r.lock();
while(num != 3) {
c3.await(); //c3等待
}
System.out.print("H");
System.out.print("T");
System.out.print("M");
System.out.print("L");
System.out.print("\r\n");
num = 1;
c1.signal(); //把c1唤醒
r.unlock();
}
}
14、 线程组 ThreadGroup
* Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
* 默认情况下,所有的线程都属于主线程组。
public class Demo08_ThreadGroup {
public static void main(String[] args) {
ThreadGroup tg = new ThreadGroup("我是一个线程组"); //创建一个线程组
// 参数 ThreadGroup对象 、MyRunnable对象 、线程的名字
Thread t1 = new Thread( tg, new MyRunnable(), "张三" ); //将指定线程加入指定组
Thread t2 = new Thread( tg, new MyRunnable(), "李四" );
//对整组进行设置
//tg.setDaemon(true); //这两条线程都变成守护线程
System.out.println(tg.getName());//输出 "我是一个线程组"
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for( int i = 0;i < 1000;i++ )
System.out.println(Thread.currentThread().getName() + "..." + i);
}
}