attetion:观看黑马视频笔记笔记总结
快捷键集合:
1.MyThread继承Thread,重写run方法 选中Mythread,ctrl+1
2.选中某个要输出的东西,然后alt+/就可以输出了
3.出现异常
eg:
Thread.Sleep(1000)
选中它,ctrl+1->add throws declaration4.Ctrl+shift+t看源码
eg:看Thread的源码
搜索Thread就可以了,选中Thread ctrl+OSleep是一个静态方法,所以类名.Sleep()就可以运行了???
5.zijiemaduixiang.
多线程
* 01.什么是线程
* 线程是程序执行的一条路径, 一个进程中可以包含多条线程
* 多线程并发执行可以提高程序的效率, 可以同时完成多项工作
* 02.多线程的应用场景
* 红蜘蛛同时共享屏幕给多个电脑
* 迅雷开启多条线程一起下载
* QQ同时和多个人一起视频
02.2多线程并行和并发的区别
* 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)
* 并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行, 由于时间间隔较短,使人感觉两个任务都在运行。
03_多线程(Java程序运行原理和JVM的启动是多线程的吗)
* A:Java程序运行原理
for(int i=0;i<1000000;i++)
{
new Demo();
}
for(int i=0;i<100000;i++)
{
system.printIn("我是多线程“);
}
class Demo{
public void finalize()
{
system.printIn(垃圾被清扫);
}//两者不是先执行一个完后,在执行另外一个,而是相互间隔执行的。
* B:JVM的启动是多线程的吗
* JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。
04_多线程程序实现的方式1
* 1.继承Thread
* 定义类继承Thread
* 重写run方法
* 把新线程要做的事写在run方法中
* 创建线程对象
* 开启新线程,( 就是调用start()方法,相当于一个发令枪) 内部会自动执行run方法public class Demo_2Thread { public static void main(String[] args) { MyThread mt = new MyThread(); //4,创建Thread类的子类对象 mt.start(); //5,开启线程 for(int i = 0; i < 1000; i++) { System.out.println("bb"); } } } class MyThread extends Thread { //1,继承Thread public void run() { //2,重写run方法 for(int i = 0; i < 1000; i++) { //3,将要执行的代码写在run方法中 System.out.println("aaaaaaaaaaaa"); } } }
05_多线程(多线程程序实现的方式2)
官话:
创建线程的另一种方法是声明实现
Runnable
接口的类。该类然后实现run
方法。然后可以分配该类的实例,在创建Thread
时作为一个参数来传递并启动。
2.实现Runnable
* 定义类实现Runnable接口
* 实现run方法
* 把新线程要做的事写在run方法中
* 创建自定义的Runnable的子类对象
* 创建Thread对象, 传入Runnable
* 调用start()开启新线程, 内部会自动调用Runnable的run()方法public class Demo3_Thread { public static void main(String[] args) { MyRunnable mr = new MyRunnable();//4,创建Runnable的子类对象 //Runnable target = mr; Thread t = new Thread(mr); //5,将其当作参数传递给Thread的构造函数 t.start(); //6,开启线程 for(int i = 0; i < 1000; i++) { System.out.println("bb"); } } } class MyRunnable implements Runnable { //1,定义一个类实现Runnable @Override public void run() { //2,重写run方法 for(int i = 0; i < 1000; i++) { //3,将要执行的代码写在run方法中 System.out.println("aaaaaaaaaaaa"); } } }
补充:
start是开启线程的,开启后,会自动调用里面的run方法(里面是执行的代码)
start相当于发令枪,run方法是运动员的各种表现(但是你还是要写run方法的)
不管怎么样的目的都是达到看到运动员的表演,不过start是他们的开关
06_多线程(实现Runnable的原理)
* 查看源码
* 1,看Thread类的构造函数,传递了Runnable接口的引用按住Thread,Ctrl:
* 2,通过init()方法找到传递的target给成员变量的target赋值This.target=Target也就是说局部变量赋值给了成员变量
点击target
可以看出这个成员变量使用Runnable声明的
结论:mr这个对象的值一层的一层的传递到了Runable target这个值
但这个值有什么作用按住Target ctrl+o搜索run方法
* 3,查看run方法,发现run方法中有判断,如果target不为null就会调用Runnable接口子类对象的run方法
target.run就是mr.run(),
target.run相当于 Runnable target=mr(父类的引用指向子类对象)
target.run编译时父类,运行是子类************************************
07_多线程(两种方式的区别)
* 查看源码的区别:
* a.继承Thread : 由于子类重写了Thread类的run(), 当调用start()时, 直接找子类的run()方法
* b.实现Runnable : 构造函数中传入了Runnable的引用, 成员变量记住了它, start()调用run()方法时内部判断成员变量Runnable的引用是否为空, 不为空编译时看的是Runnable的run(),运行时执行的是子类的run()方法* 继承Thread
* 好处是:可以直接使用Thread类中的方法,代码简单
* 弊端是:如果已经有了父类,就不能用这种方法?????
* 实现Runnable接口实现线程呢,第二个方式由于没有继承thread,要如何实现呢
1.把这个Runnable的子类对象传到Thread的构造方法中,
传递到里面的话,因为这个子类对象中会包含run方法,
有一个判断,如果子类对象不是空的话,JVM自动会调用run方法
* 好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的(understand)
* 弊端是:不能直接使用Thread中的方法,需要先获取到线程对象后,才能得到Thread的方法,代码复杂
08_多线程(匿名内部类实现线程的两种方式)
为什么用匿名内部类,不用找一个类去继承thread类了,值接new Thread 或者new Runnable接口就可以了,
然后只需要重写里的一个方法就可以了,简便????
* 继承Thread类
new Thread() { //1,new 类(){}继承这个类,这就是继承Thread这个类
public void run() { //2,重写run方法
for(int i = 0; i < 3000; i++) { //3,将要执行的代码,写在run方法中
System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa");
}
}
}.start(); //开启线程****上面的整个就代表thread的子类对象
* 实现Runnable接口
new Thread(new Runnable() { //1,将Runnable的子类对象传递给Threadde 构造方法
public void run() { //2,重写run方法
for(int i = 0; i < 3000; i++) { //3,将要执行的代码,写在run方法中
System.out.println("bb");
}
}
}).start();
09_多线程(获取名字和设置名字)
* 1.获取名字
* 通过getName()方法获取线程对象的名字
* 2.设置名字
* 通过构造函数可以传入String类型的名字
*
new Thread("xxx") {
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println(this.getName() + "....aaaaaaaaaaaaaaaaaaaaaaa");
}
}
}.start();
///******这的This就相当于匿名内部类的对象谁来调用我我就代表谁
new Thread("name") {
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println(this.getName() + "....bb");
}
}
}.start();
* 通过setName(String)方法可以设置线程对象的名字
*下面的方式是父类的引用指向子类对象:
Thread t1 = new Thread() {
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println(this.getName() + "....aaaaaaaaaaaaaaaaaaaaaaa");
}
}
};
Thread t2 = new Thread() {
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println(this.getName() + "....bb");
}
}
};
t1.setName("芙蓉姐姐");
t2.setName("凤姐");
t1.start();
t2.start();
10_多线程(获取当前线程的对象)
用Getname的话,因为是thread的方法,runnable用不了,上面的08有例子 想办法获得当前正在执行的线程
//与runnable接口有关系
* Thread.currentThread(), 利用它主线程也可以获取
*
new Thread(new Runnable() {
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + "aaaaaaa");
}
}
}).start();
Thread.currentThread().setName("我是主线程"); //获取主函数线程的引用,并改名字
System.out.println(Thread.currentThread().getName()); //获取主函数线程的引用,并获取名字
11_多线程(休眠线程)
* Thread.sleep(毫秒,纳秒), 控制当前线程休眠若干毫秒1秒= 1000毫秒 1秒 = 1000 * 1000 * 1000纳秒 1000000000Sleep是一个静态方法,所以类名.Sleep()就可以运行.
new Thread() { public void run() { for(int i = 0; i < 10; i++) { System.out.println(getName() + "...aaaaaaaaaaaaaa"); try { Thread.sleep(10); //这个时候打印的话是10个a,10个b,但如何体现多线程呢,CPU执行的太快了 //想要看出切换的过程,让cpu到执行我这个程序时候睡一会, //但是这里会有异常,因为正走着走着,睡着了,属于中断异常 //这里异常是抛还是抓呢,Thread的run方法是没有抛异常的, //new Thread这里面的东西是Thread的子类对象,属于而儿子级别 //所以,父亲坏了,儿子不能比父亲更坏的例子 //儿子必须自己处理,用try catch //当cpu执行到这的时候,发现他睡了,就把 执行权切换给第二个,第二个也是睡的,有切换回去, //如果发现第一个醒了,就把执行权利给第一个 } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); new Thread() { public void run() { for(int i = 0; i < 10; i++) { System.out.println(getName() + "...bb"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start();
12_多线程(守护线程)
* setDaemon(), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出
//非守护线程;帅
//守护线程:兵,象,马
*Thread t1 = new Thread() { public void run() { for(int i = 0; i < 50; i++) { System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaaaa"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } }; Thread t2 = new Thread() { public void run() { for(int i = 0; i < 5; i++) { System.out.println(getName() + "...bb"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } }; t2.setDaemon(true); //将t1设置为守护线程 t1.start(); t2.start();
//t2.setDaemon(true); //参数传递为true将t2设置为守护线程
//如果非守护线程挂掉了。那么守护线程也会有一段时间缓冲区自杀的过程消耗时间,会有个时间缓冲,也会随之死亡。
//所以像上面的例子中,aaa的次数执行完成后,bb会过上一小会才会挂掉.
13_多线程(加入线程)
* join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续(插队)
* join(int), 可以等待指定的毫秒之后继续//匿名内部类在使用它的局部变量的时候必须用final修饰,因为局部变量的生命周期跟不上,
final Thread t1 = new Thread() { public void run() { for(int i = 0; i < 50; i++) { System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaaaa"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } }; Thread t2 = new Thread() { public void run() { for(int i = 0; i < 50; i++) { if(i == 2) { try { //t1.join(); //插队,加入,插队过来是一个异常 t1.join(30); //加入,有固定的时间,过了固定时间,继续交替执行 //这个是join的重载方法,里面 传递的是毫秒值 Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(getName() + "...bb"); } } };//这个是属于一个匿名内部类,t1属于一个局部变量,匿名内部类在使用一个它所在方法中的局部变量的 时候必须用final修饰 ///匿名内部类就是我们的局部内部类 t1.start(); t2.start();
14_多线程(礼让线程)
* yield让出cpuThread.yiead();
15_多线程(设置线程的优先级)
* setPriority()设置线程的优先级
看源码:ctrl+shift_+t
下来ctrl+ot1.setPriority(Thread.MIN_PRIOITY)
//将t1这条线程设置为最小优先级
video补充笔记
16_多线程(同步代码块)
* 1.什么情况下需要同步
* 当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
* 如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.
* 2.同步代码块
* 使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块
* 多个同步代码块如果使用相同的锁对象, 那么他们就是同步的
注意:
final Printer pri=new Printer();//final 不要忘记了匿名内部类用方法的局部变量
Demo d = new Demo();锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
就是在()直接写一个new Demo()。
attetion:location of Demo.
It shoud be after Class.
public static vodi main(String args[]) { final Printer pri=new Printer();//final 不要忘记了,匿名内部类用方法的局部变量 new Thread()//创建一个线程 { public vodi run() { while(true) { pri.print1(); } } }.start(); new Thread()//创建另外一个线程 { public vodi run() { while(true) { pri.print2(); } } }.start(); } class Printer { Demo d = new Demo(); public static void print1() { synchronized(d){ //锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象 System.out.print("西"); //锁对象不能是匿名对像,已经为匿名对象不是同一对像,类似于直接在()中直接传递new Demo()这种,是不可以的。 System.out.print("安"); System.out.print("邮"); System.out.print("电"); System.out.print("\r\n"); } } public static void print2() { synchronized(d){ System.out.print("西"); System.out.print("安"); System.out.print("财"); System.out.print("经"); System.out.print("\r\n"); } } }
24.17_多线程(同步方法)
//public synchroinzed void print1()//同步方法只需要在方法上加上synchroinzed;
* 使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的
//非静态的同步方法的 锁的对像是神马?//答案:锁对象是this
静态方法中的锁对象能不能是this呢,
不能,因为静态是优先于对象存在的,静态方法中是不可以定义this和super的
this是当你去创建对象的时候才有值的
静态方法是随着类的加载而加载的,类加载的时候有对象,
对象就是那个字节码对象,就是class 后面的东西.class,就是类名.class
//西安邮电中有一个锁对象,第二个财经可以在class后定义一个静态的锁对象,然后传递进来,也可以用字节码对象,为了一致选择第一个
class Printer2 { Demo d=new Demo(); //非静态的同步方法的 锁的对像是神马,如果程序运行时结果不穿插,说明则是Object对象,任意的,如d;但是结果是乱序的 //答案: public synchroinzed void print1() //该方法中隐含了一个指针,this,谁调用我我就指向谁 { System.out.print("西"); System.out.print("安"); System.out.print("邮"); System.out.print("电"); System.out.print("\r\n"); } public void print2() { synchroinzed(this) { System.out.print("西"); System.out.print("安"); System.out.print("财"); System.out.print("经"); System.out.print("\r\n"); } } //第二个加上了this结果就正确了 class Printer { public static void print1() { synchronized(Printer.class){ //锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象 System.out.print("西"); System.out.print("安"); System.out.print("邮"); System.out.print("电"); System.out.print("\r\n"); } } /* * 非静态同步函数的锁是:this * 静态的同步函数的锁是:字节码对象 */ public static synchronized void print2(Printer.class) { System.out.print("西"); System.out.print("安"); System.out.print("财"); System.out.print("经"); System.out.print("\r\n"); } } //静态的: class Printer2 { public static synchroinzed void print1() { System.out.print("西"); System.out.print("安"); System.out.print("邮"); System.out.print("电"); System.out.print("\r\n"); } public void print2() { synchroinzed(Printer.class) { System.out.print("西"); System.out.print("安"); System.out.print("财"); System.out.print("经"); System.out.print("\r\n"); } } //西安邮电中有一个锁对象,第二个财经可以在class后定义一个静态的锁对象,然后传递进来,也可以用字节码对象,为了一致选择第一个
18_多线程(线程安全问题)
* ******多线程并发操作同一数据时, 就有可能出现线程安全问题
* 使用同步技术可以解决这种问题, 把操作数据的代码进行同步, 不要多个线程一起操作main() { new Ticket.start(); new Ticket.start(); new Ticket.start(); new Ticket.start(); }
1.上面的问题会导致每个卖400张,4个对象,每个人100张,所以加上static使之成为共享的
private static int ticket=100;
2.但是问题不明显,加上Sleep后来看一下,同时加上try catch (选中sleep选择第一个)但会导致卖到-1000多张票,
解决办法:
加上睡眠的方法是模拟那些行执行n多行程序的情况
出现问题的原因:运行到ticket=1,跳转到try的Sleep中
遇到Sleep,线程一睡,同时,其他三个线程一样都睡觉
此时线程一的时候睡醒了,ticket=0,其他三个过来就会从0开始不断地往下-,他就会执行输出语句ticket--,其他三个一样,跳过ticket==0这一条件
3.加上<=0也会有问题
跟上面情况一样,可能线程3睡醒了,但是他还是会执行ticket--的操作也会有几个负数,然后再去判断if的条件,还是会有几个负数
4.此时涉及到同步的问题:if(tickets <= 0) break; try { Thread.sleep(10);//线程1睡,线程2睡,线程3睡,线程4睡 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName() + "...这是第" + tickets-- + "号票");
给这段加上同步
令if的ticket的和输出的ticket加上同步代码块
synchronized(this),加到if的前面
但是问题依旧
因为this代表new ticket,但是他有四个,每个线程都会有自己的对象,每次new一次都会是不同的对象
加上Ticket.class就可以了给同步代码快的括号中
加上static Object obj也是可以的额,但是第二个最好。
class Ticket extends Thread{
private int ticket=100;
public void run()
{
while(true)
{
if(ticket==0)
{
break;
}
system.out printIn(getname()+"This is the "+ ticket--"tickets");
}
}}
public class Demo2_Synchronized {/**
* @param args
* 需求:铁路售票,一共100张,通过四个窗口卖完.
*/
public static void main(String[] args) {
TicketsSeller t1 = new TicketsSeller();
TicketsSeller t2 = new TicketsSeller();
TicketsSeller t3 = new TicketsSeller();
TicketsSeller t4 = new TicketsSeller();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t4.setName("窗口4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//加static让四个线程共享的变量
class TicketsSeller extends Thread {
private static int tickets = 100;
static Object obj = new Object();
public TicketsSeller() {
super();
}
public TicketsSeller(String name) {
super(name);
}
public void run() {
while(true) {
synchronized(obj) {
if(tickets <= 0)
break;
try {
Thread.sleep(10);//线程1睡,线程2睡,线程3睡,线程4睡
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "...这是第" + tickets-- + "号票");
}
}
}
}
19_多线程(火车站卖票的例子用实现Runnable接口)
//tickets不用加static,因为对象只创建了一次,一个mt
class MyTicket implements Runnable{ private int tickets=100;//这里不用定义为static , 因为对象只创建了一次 public void run() { while(true) { synchronized(Ticket.class) {//或者this都是可以的 if(tickets <= 0) break; try { Thread.sleep(10);//线程1睡,线程2睡,线程3睡,线程4睡 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName() + "...这是第" + tickets-- + "号票"); }//上面的哪一行加上Thread.currentThread.getName(),因为不是Thread的子类 } }}} main() { MyTicket mt=new MyTicket(); new thread(mt).start(); new thread(mt).start(); new thread(mt).start(); new thread(mt).start(); }
但是另外一种情况
Thread t1=new Thread(mt);
t1.start();
t1.start();
t1.start();
t1.start();
这样把mt传进来,这样会有异常,但是是非法的,一个线程多次启用是违法的,就像是刘翔跑完100,在重新跑100的问题。
.20_多线程(死锁)
* 多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁
* 尽量不要嵌套使用
private static String s1 = "筷子左";
private static String s2 = "筷子右";
public static void main(String[] args) {
new Thread() {
public void run() {
while(true) {
synchronized(s1) {
System.out.println(getName() + "...拿到" + s1 + "等待" + s2);
synchronized(s2) {
System.out.println(getName() + "...拿到" + s2 + "开吃");
}
}
}
}
}.start();
new Thread() {
public void run() {
while(true) {
synchronized(s2) {
System.out.println(getName() + "...拿到" + s2 + "等待" + s1);
synchronized(s1) {
System.out.println(getName() + "...拿到" + s1 + "开吃");
}
}
}
}
}.start();
}