今日课程:多线程
程序:由逻辑代码和数据组成的集合,存储在磁盘上,是静态的。
进程:正在运行的程序,需要在内存中开辟空间,需要CPU的调度。对于CPU来说,某一时刻他只能执行一个进程,看到的同时的执行的效果,是因为CPU一直在做着高速的切换操作。
线程:可以独立运行的代码片段,属于进程。多个可独立运行的代码片段称之为多线程。一个进程中至少要有一个线程---单线程的进程。
对于之前所写的java程序,都是单线程程序,可独立运行的代码都被封装到main方法中,要创建新的线程,代码都要封装到一个run方法中。
并行:多个任务共同运行,需要多个CPU支持。
并发:多个任务共同运行,只有一个CPU调度。
多线程这种做法提高了CPU的使用率,但是对于某个进程来说可能时间稍长。
多线程执行时,某一线程出现异常,他会停止运行,对其他线程没有影响。
创建线程的两种方式:继承和实现
1.继承Thread类
- 自定义一个类extends Thread
- 重写run()方法
- 创建子类对象
- 调用Thread类中的start()方法,启动线程
2.实现接口方式
- 定义一个实现类实现Runnable接口
- 实现接口中的run()方法
- 创建实现类对象
- 创建Thread类对象,把实现类对象当做参数传入
- 调用Thread类中的start方法
两种方式的异同:
继承Thread类方式
public class Demo1 {
public static void main(String[] args) {
// myThread my=new myThread();
// my.start();//启动线程
// 匿名内部类形式创建多线程
new Thread() { //父类名,子类方法
@Override
public void run() {
for (long i = 0; i < 100L; i++) {
System.out.println("i=" + i);
}
}
}.start();
for (long i = 0; i < 100L; i++) {
System.out.println("main=" + i);
}
}
}
class myThread extends Thread { // 继承方式实现多线程
public void run() {
for (long i = 0; i < 100L; i++) {
System.out.println("i=" + i);
}
}
}
实现类方式:
public class Demo2 {
public static void main(String[] args) {
// myThread my=new myThread();//创建自定义类的对象
// Thread th=new Thread(my);//把自定义类的对象当做参数传给Thread类的构造参数
// th.start();//启动线程
//匿名内部类形式
/*一层一层分析,首先最外层的是Thread对象,先new他,然后传入的是myThread对象作为参数,我们使用匿名方法,所以用他的
父接口作为new的对象,所以这里要new Runnable(),然后我们再用大括号括起来,大括号里面在写他的实现类myThread改写的
run()方法,最后再.start()调用方法即可*/
new Thread(new Runnable(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("多线程--:" + i);
}
}
}).start();
for (int i = 0; i < 100; i++) {
System.out.println("主线程:"+i);
}
}
}
class myThread implements Runnable {
// 实现接口形式多线程
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("多线程--:" + i);
}
}
}
从代码上看:
- 继承方式代码简单
- 实现方式代码稍麻烦
从设计角度看:
- 继承方式,通过查看start方法的源码,看到了start0方法,该方法是本地方法,不是java语言实现的,可以理解为是JVM启动线程并回来调用我们写的run方法。
- 实现方法,通过查看源码发现Thread类中有一个Runnable类型的引用变量target是通过我们编写的Runnable的实现类对象进行复制的,调用start方法时找到了start0,JVM回来调用run方法,发现源码中run方法源码中有一个target.run(),即是父接口类型引用指向实现类对象,即最终调用的是我们编写的run方法。
扩展性
- 继承方式:由于java是单继承,因此不能再继承其他类,也有可能某个类需要以线程方式运行,但是与Thread不具备子父类关系,违背继承原则,因此不建议使用该方法
- 实现方式,还可以去继承其他类,线程对象和任务分离开,耦合度降低,建议使用该方法。
匿名内部类方式创建线程
代码参考上面两个。
Thread类中的常用构造方法:
Thread() 分配新的 Thread 对象。 |
Thread(Runnable target) //传入一个实现了Runnable接口的对象实例分配新的 Thread 对象。 |
Thread(Runnable target, String name) //传入一个实现了Runnable接口的对象实例和新线程的名字分配新的 Thread 对象。 |
Thread(String name) //创建时传入新线程的名字分配新的 Thread 对象 |
Thread类中的常用方法:
static Thread |
currentThread() //静态方法,直接Thread.currentThread()调用返回对当前正在执行的线程对象的引用。当前谁调用了这个线程就显示谁的名字。 |
String |
getName() 返回该线程的名称。 |
void |
该方法用在线程启动前启动后都可以,即使不设置线程的名字,创建的线程也有默认名称:Thread-编号(从0开始) 主线程名字是:main |
线程优先级:优先级有1-10级,优先级高的先执行,低的后执行,创建的线程默认优先级为5,主线程main优先级也是5。
void |
setPriority(int newPriority) 设置线程的优先级。 |
int |
getPriority() 返回线程的优先级。 |
线程休眠:是当前线程休眠设置的毫秒值,时间到了则线程恢复CPU调度。
static void |
sleep(long millis) //静态方法,直接Thread.sleep()调用在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 |
守护线程(后台线程):
创建的线程默认为非守护线程,任何线程都可以设置成守护线程和非守护线程。守护线程可以理解为:为了一个线程(前台线程)的存在而存在,如果不存在了守护线程也会退出。
void |
setDaemon(boolean on) 将该线程标记为守护线程或用户线程。 |
boolean |
isDaemon() 测试该线程是否为守护线程。 |
public class Demo3 {
public static void main(String[] args) {
Ticket ticket = new Ticket();//创建实现类对象
// Thread t1 = new Thread(ticket, "线程1:");//实现类对象作为参数传入,同时为线程指定名字,
// Thread t2 = new Thread(ticket, "线程二:");
Thread t1 = new Thread(ticket);//创造对象时没有传入名字的话调用名字是默认的名字
Thread t2 = new Thread(ticket);
System.out.println("当前线程的名字为:"+t1.getName());
System.out.println("当前线程的名字为:"+t2.getName());
System.out.println("----------------------------------");
//给线程重新命名
t1.setName("new线程1- ");//给线程命名放在启动前后效果一样
t2.setName("new线程二--");
//获取优先级
System.out.println("t1的优先级为:---"+t1.getPriority());
System.out.println("t2的优先级为:---"+t2.getPriority());
System.out.println("----------------------------------");
//设置优先级,注意设置了休眠后会严重影响优先级的顺序
t1.setPriority(10); //t1出现的几率远大于t2,知道100个数都遍历完了,t2可能也出现不了几次
t2.setPriority(1);
//守护线程
t1.setDaemon(true);
System.out.println("t1是否是守护线程?---"+t1.isDaemon());
System.out.println("t2是否是守护线程?---"+t2.isDaemon());
System.out.println("----------------------------------");
//启动线程
t1.start();
t2.start();
}
}
class Ticket implements Runnable {
int ticket = 100;
public void run(){
while (ticket > 0) {
try {
Thread.sleep(100);//休眠,静态方法,直接调用
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + ticket);
ticket--;
}
}
}
多线程出现的安全问题
问题:多线程执行时,操作的是相同的代码,这片相同的代码操作的还是对线程共享的数据,某一线程没有执行完这片代码,另一个线程就参与执行,导致了错误数据产生。--安全问题
解决方式:使用用同步代码块。
synchronized(锁对象){
多线程共同执行的共享数据代码;
}
好处:解决了多线程的安全问题。
弊端:执行效率降低了。
- 前提:至少是2个以上线程
- 保证是同一个锁对象,锁对象可以是任意对象,也可以是this
synchronized修饰符,同步的意思,还可以修饰方法:
当用synchronized修饰方法时,这个方法就具备了同步的属性,不会出现多线程的安全问题。
synchronized 返回值类型 方法名(){ //锁对象为this
语句;
}
synchronized 返回值类型 方法名(){ //锁对象为 类名.class
语句;
}
public class Demo4 {
public static void main(String[] args) {
TTicket tick = new TTicket();
Thread t1 = new Thread(tick, "线程一号");
Thread t2 = new Thread(tick, "线程2号--");
Thread t3 = new Thread(tick, "线程三号--");
Thread t4 = new Thread(tick, "线程4号--");
Thread t5 = new Thread(tick, "线程五号--");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
class TTicket implements Runnable {
private int ticket = 100;
// Object obj = new Object();// 锁对象,任意对象都可以
public void run() {
/*注意:这里判断条件不能写ticket>0,因为这样每一个线程都能抢到一个值,只是执行语句被线程锁锁住了没法输出,但是当某一个
线程执行到ticket=1时返回的下一个值是0,这时该线程退出,但是其他线程已经进入了循环,而且此时线程锁也是打开的,所以他们会
继续输出,返回的值为负数,不满足while条件,退出,但是还有线程已经在循环内等待执行,所以他会拿返回的负数值继续操作,
直到所有线程都结束才程序才结束,所以最后会输出0或者是负值,因此判断条件一定要放在同步锁内部,用if来判断。*/
while (true) {
/*加一个同步锁,保证线程安全,同步锁必须放在while内部,因为如果放在外部,第一个线程抢到资源以后同步锁就锁住,然后内部
直接开始循环,循环没结束锁是不会打开的,直到循环结束以后同步锁打开,线程二抢到锁,然后进入循环,但是此时ticket已经为0,
循环直接结束,所以线程二始终都没有输出东西*/
// synchronized (this) { //1= 开 ,0 =关
// if (ticket > 0) {
// // 打印输出this都是同一个值,验证了同步锁必须是同一对象
// System.out.println(Thread.currentThread().getName() +"拿到的票为:"+ ticket + "..." + this);
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// ticket--;
// }else
// break;
// }
lock(); //还可以单独写一个方法来实现同步锁
if(ticket<1) //结束无线循环,不能采用lock方法中加return的方法,那样只能结束lock方法的执行而已
break;
}
}
public synchronized void lock(){ //
if(ticket > 0){ //判断条件一定要写在循环内部
System.out.println(Thread.currentThread().getName()+"---"+ticket +"...."+this);
try {
Thread.sleep(100); //t2
} catch (InterruptedException e) {
}
ticket--;
}
// else{ //注意:return是结束方法的执行的,只能结束本方法,由于调用lock方法的run方法还有一个
// return; //所以run方法的无线循环是结束不掉的,必须再加判断
// }
}
}
死锁
多线程想要对方的锁,但对方都不释放锁,导致程序运行卡住不在继续运行的现象。实际开发中不能出现死锁的现象,尽量避免同步锁的嵌套使用。
eg:同步的嵌套
//死锁
public class Demo8{
public static void main(String[] args) {
deadLock d1=new deadLock(true);//先获取a锁再获取b锁
deadLock d2=new deadLock(false);//先获取b锁再获取a锁
Thread t1=new Thread(d1);
Thread t2=new Thread(d2);
t1.start();
t2.start();
}
}
class deadLock implements Runnable {
boolean flag;
public deadLock(boolean flag){
this.flag=flag;
}
public void run() {
if (flag) {
synchronized ("a") {
System.out.println("if----------a");
synchronized ("b") {
System.out.println("if------------b");
}
}
}else
synchronized ("b") {
System.out.println("else==========b");
synchronized ("a") {
System.out.println("else===========a");
}
}
}
}
//匿名内部类方式
public class Demo7 {
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
synchronized ("刀叉") {
System.out.println(Thread.currentThread().getName() + ":你不给我筷子,我就不给你刀叉");
synchronized ("筷子") {
System.out.println(Thread.currentThread().getName() + ": 给你刀叉");
}
}
}
}, "中国人").start();
new Thread(new Runnable() {
public void run() {
synchronized ("筷子") {
System.out.println(Thread.currentThread().getName() + ": 先给我刀叉");
synchronized ("刀叉") {
System.out.println(Thread.currentThread().getName() + ": 筷子给你");
}
}
}
}, "美国人").start();
}
}