1:多线程(理解)
(1)线程是依赖于进程而存在的。
A:进程 正在运行的应用程序
B:线程 进程的执行路径,执行单元
(2)多线程的两种方案:(掌握)
请参照:多线程两种方式
继承Thread类(查看api简单介绍Thread类):
package com.edu_02;
public class MyThread extends Thread{
//1.继承Thread类
//2.重写run方法,重写run方法中的代码之后,当我们启动了这个线程之后,我们的这个线程就会执行run方法中的代码
@Override
public void run() {
//需求:开启该线程之后,执行一个for循环
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
package com.edu_02;
public class Test {
public static void main(String[] args) {
//只要我们创建了一个线程对象,并且启动该线程的实例,我们就相当于开启了一个线程
MyThread mt = new MyThread();
mt.start();//1.开启了一个线程 2.让开启的这个线程执行他对应的类中的run方法
//再次创建一个子线程,并开启这个子线程执行他的run方法
MyThread mt2 = new MyThread();
mt2.start();
}
}
实现Runable接口:
package com.edu_03;
public class MyThread implements Runnable{
@Override
public void run() {
//启动该线程对象之后,需要执行的代码
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
package com.edu_03;
public class Test {
public static void main(String[] args) {
//创建Mythread对象
MyThread mt = new MyThread();
//开启这个线程
//mt.start();//这里的这个类仅仅是实现了Runnalble接口的一个类,但是start方法在Thread类中
//但是我们想要开启一个线程,就必须调用start方法,请问怎么办?
//public Thread(Runnable target)
Thread t1 = new Thread(mt);
t1.start();
}
}
(3)多线程的几个问题:
A:启动线程用的是哪个方法
start()
B:start()和run()的区别
start():1.开启线程 2.执行run()方法里面的代码
run():执行的是线程里面执行的代码,并不会开启线程
C:为什么要重写run()
因为每个线程需要执行的代码都是都是不一样的,
我们需要将每个线程自己独立执行的代码写到run()方法中执行
D:线程可以多次启动吗
如果是不同的线程对象,是可以同时开启的
package com.edu_04;
public class Test {
public static void main(String[] args) {
//B:start()和run()的区别
// 创建一个线程对象
MyThread mt = new MyThread();
MyThread mt2 = new MyThread();
//mt.start();//1.首先开启了一个独立的线程 2.让这个独立的线程执行run方法中的代码
System.out.println("--------");
//mt.run();//1.普通的创对象,调方法 2.代码实在主线程执行,不会开辟新线程
//D:线程可以多次启动吗
mt.start();
//mt2.start();//如果是不同的线程对象,是可以同时开启的
//mt.start();//线程是不能多次启动的
}
}
package com.edu_04;
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
(4)线程的调度和控制
线程休眠(Thread.sleep(毫秒值))
线程名称(setName(),getName();)
线程的调度及优先级setPriority(10)(注意默认值是5,区间在1-10之间)
什么叫线程优先级:说白了就是设置你抢占cpu执行权抢占到的概率
package com.edu_05;
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//线程休眠(Thread.sleep(毫秒值))
try {
Thread.sleep(1000);//在此处出现的异常我们只能抓取,不能抛出
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//获取执行线程的姓名
System.out.println(this.getName()+i);
}
}
}
package com.edu_05;
public class Test {
public static void main(String[] args) {
//线程名称(setName(),getName();)
//创建3个线程对象
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
//给三个线程设置姓名
t1.setName("刘备");
t2.setName("张飞");
t3.setName("关羽");
//设置线程的优先级
//线程的调度及优先级setPriority(10)(注意默认值是5,区间在1-10之间)
//t1.setPriority(100);//设置的区间必须在1-10之间
t1.setPriority(10);
//开启线程
t1.start();
t2.start();
t3.start();
}
}
package com.edu_06;
public class MyThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//首先调用方法获取到执行该run方法的线程对象,然后调用方法获取该线程对象的名称
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
package com.edu_06;
public class Test {
public static void main(String[] args) {
//创建三个线程并且启动三个线程
MyThread mt = new MyThread();
//创建线程对象
Thread t1 = new Thread(mt);
Thread t2 = new Thread(mt);
Thread t3 = new Thread(mt);
//给线程设置姓名
t1.setName("刘备");
t2.setName("关羽");
t3.setName("张飞");
//启动线程
t1.start();
t2.start();
t3.start();
}
}
(5)多线程案例(两种方式实现,睡一会出现线程安全问题):
5.1继承Thread卖票
package com.edu_07;
public class Test {
public static void main(String[] args) {
//创建三个线程模拟三个售票窗口
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
MyThread mt3 = new MyThread();
//给线程设置名称
mt1.setName("窗口一");
mt2.setName("窗口二");
mt3.setName("窗口三");
//启动线程,开启售票
mt1.start();
mt2.start();
mt3.start();
}
}
package com.edu_07;
public class MyThread extends Thread{
//共有100张票,将ticket改为静态之后,被类的所有对象所共享
static int ticket = 100;
@Override
public void run() {
//用一个while true循环模拟三个窗口一直处于打开的状态
while (true) {
//只有当ticket>0的时候,才可以出售票
if (ticket>0) {
System.out.println(getName()+"正在出售第:"+ticket--+"张票");
}
}
}
}
5.2实现Runnable卖票(睡一会出现线程安全问题)
package com.edu_08;
public class Test {
public static void main(String[] args) {
//创建MyThread这个对象
MyThread mt = new MyThread();
//创建3个窗口
Thread t1 = new Thread(mt);
Thread t2 = new Thread(mt);
Thread t3 = new Thread(mt);
//给线程对象设置名称
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
//启动窗口开始售票
t1.start();
t2.start();
t3.start();
}
}
package com.edu_08;
public class MyThread implements Runnable{
int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket>0) {
System.out.println(Thread.currentThread().getName()+"正在出售第:"+ticket--+"张票");
}
}
}
}
问题:
按照真实的情景加入了延迟,却发现出现了这样的两个问题:
A:相同的票卖了多次
CPU的一次操作必须是原子性的(操作是CPU执行一次就可以直接完成的)
B:出现了负数的票
随机性和延迟导致的
出现上面的问题称为线程安全问题。
package com.edu_09;
/**
* 总结:什么时候会出现线程安全问题?
* 1.存在多线程的情况
* 2.多个线程之间存在共享数据
* 3.存在多条语句操作共享数据
*
* 在我们自己的程序中存在线程安全问题吗?存在。。
* 1.存在多线程的情况
* 存在
* 2.多个线程之间存在共享数据
* 存在
* 3.存在多条语句操作共享数据
* 存在
*
*
* 如何解决线程安全问题??
* 1.存在多线程的情况
* 改不了
* 2.多个线程之间存在共享数据
* 改不了
* 3.存在多条语句操作共享数据
* 可以改变
*
*
*(7)如何解决多线程安全问题(掌握)
注意:线程安全执行效率就低,线程不安全,执行效率高
A:同步代码块(测试不是同一个锁的情况,测试是同一个锁的情况)
synchronized(对象) {
需要被同步的代码。
}
* 1.对象?
* 答:任意对象,这个对象就被成为锁
* 2.需要被同步的代码?
* 答:会出现线程安全问题的代码。
* 3.注意:同步代码块对这个锁是有要求的
* 答:需要多个线程共享同一把锁
*/
public class MyThread implements Runnable{
//定义100张票
int ticket = 100;
Object obj = new Object();
@Override
public void run() {
while (true) {
//同步代码块
//synchronized (new Object()) {//t1,t2,t3三个线程不共享同一把锁,每个线程都有自己的一把锁
synchronized (obj) {//这样3个线程才可以共享同一把锁
if (ticket>0) {
//考虑到实际的生活中,我们需要给每一个线程加入一定的延迟,模拟一下这种效果
try {
Thread.sleep(100);
/**
* 分析:为什么会出现两张100张票
* CPU的一次操作必须是原子性的(操作是CPU执行一次就可以直接完成的)
* t1抢占到cpu的执行权,此时ticket=100,但是此刻休眠了
* 此时被t2抢占到了cpu的执行权,此时ticket=100,
* t1,t2分别睡了100毫秒之后,分别醒来了。。
* t1此时出售第100张票
* t2此时出售第100张票
*/
/**
* 分析:为什么会出现0张票和-1张票
* 假设此时票池中仅剩1张票了,
* t1进来休眠了
* t2进来休眠了
* t3进来休眠了
*/
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第:"+ticket--+"张票");
/**
* t1醒来,出售的是第1张票,此时tickt=0
* t2醒来,出售第0张票,此时ticket=-1
* t3醒来,出售第-1张票,此时ticket=-2
*/
/**
* ticket--这个动作一共包含几步:
* 1.打印出ticket此刻本身的值
* 2.ticket自减1
* 3.将自减之后的ticket的最新的值赋值给变量ticket
*/
}
}
//当被同步的代码执行完毕之后,t1手里拿着的obj这个锁才会被释放,
//t1,t2,t3重新抢占cpu的执行权,谁抢到了继续拿着obj这个锁,执行同步代码块中的内容
}
}
}
package com.edu_09;
public class Test {
public static void main(String[] args) {
//创建MyThread对象
MyThread mt = new MyThread();
//创建三个窗口
Thread t1 = new Thread(mt);
Thread t2 = new Thread(mt);
Thread t3 = new Thread(mt);
//给每一个窗口设置姓名
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
//开启窗口进行售票
t1.start();
t2.start();
t3.start();
}
}
(6)多线程安全问题
A:是否是多线程环境
B:是否有共享数据
C:是否有多条语句操作共享数据
(7)如何解决多线程安全问题(掌握)
线程安全执行效率就低
A:同步代码块(测试不是同一个锁的情况,测试是同一个锁的情况)
synchronized(对象) {
需要被同步的代码。
}
需求:1.测试不是同一把锁的时候线程安全吗? 2.如果是同一把锁线程安全吗?
两个问题:1.对象是什么 ?
答:任意对象 ,相当于是一把锁,只要线程进去就把锁锁上
2.需要同步的代码?
答:被线程执行的代码
C:锁对象问题
a:同步代码块(定义一个抽象类,里面专门定义一个锁)
任意对象
b:同步方法(仅适用于实现runable接口)
public synchronized void sellTicket(){同步代码}
this
c:静态同步方法
类的字节码对象
public static synchronized void sellTicket() {
需要同步的代码
}
package com.edu_10;
public class MyThread implements Runnable{
//定义100张票
static int ticket = 100;
Object obj = new Object();
int x = 0;
@Override
public void run() {
while (true) {
if (x%2==0) {
synchronized (MyThread.class) {//这样3个线程才可以共享同一把锁
if (ticket>0) {
//考虑到实际的生活中,我们需要给每一个线程加入一定的延迟,模拟一下这种效果
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第:"+ticket--+"张票");
}
}
}else {
sellTicket();
}
x++;
}
}
// private void sellTicket() {
// synchronized (obj) {//这样3个线程才可以共享同一把锁
// if (ticket>0) {
// //考虑到实际的生活中,我们需要给每一个线程加入一定的延迟,模拟一下这种效果
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
//
// System.out.println(Thread.currentThread().getName()+"正在出售第:"+ticket--+"张票");
// }
// }
// }
//同步方法:同步方法是将synchronized关键字加到方法上,同步方法的锁是this
// private synchronized void sellTicket() {
// if (ticket>0) {
// //考虑到实际的生活中,我们需要给每一个线程加入一定的延迟,模拟一下这种效果
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
//
// System.out.println(Thread.currentThread().getName()+"正在出售第:"+ticket--+"张票");
//
// }
// }
//静态同步方法,他的锁是本类的字节码文件对象:类名.class。
private static synchronized void sellTicket() {
if (ticket>0) {
//考虑到实际的生活中,我们需要给每一个线程加入一定的延迟,模拟一下这种效果
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第:"+ticket--+"张票");
}
}
}
package com.edu_10;
public class Test {
public static void main(String[] args) {
//创建MyThread对象
MyThread mt = new MyThread();
//创建三个窗口
Thread t1 = new Thread(mt);
Thread t2 = new Thread(mt);
Thread t3 = new Thread(mt);
//给每一个窗口设置姓名
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
//开启窗口进行售票
t1.start();
t2.start();
t3.start();
}
}
(8)匿名内部类的方式使用多线程(掌握)
new Thread() {
public void run() {
...
}
}.start();
new Thread(new Runnable(){
public void run() {
...
}
}).start();
package com.edu_11;
public class NiMingThread {
public static void main(String[] args) {
//方式1:
new Thread(){
//重写的方法
@Override
public void run() {
for (int i = 0; i <10; i++) {
System.out.println(i);
}
}
}.start();
//方式2
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <10; i++) {
System.out.println(i);
}
}
}).start();
}
}
案例:利用匿名内部类,启动多个线程,验证单例设计模式之懒汉式所存在的缺陷,当使用多线程来搞的时候就不单例了。
package com.edu_12;
public class Test {
public static void main(String[] args) {
//启动第一个线程
new Thread(){
@Override
public void run() {
System.out.println(SingleIntanceDemo.getInstance());
//com.edu_12.SingleIntanceDemo@2d7fc1e7
}
}.start();
//启动第二个线程
new Thread(){
public void run() {
System.out.println(SingleIntanceDemo.getInstance());
//com.edu_12.SingleIntanceDemo@2a8b83e3
};
}.start();
}
}
package com.edu_12;
/**
* 面试中单例设计模式,我们该写哪一个?
* 懒汉
*
* 工作中我们用的时候,用哪个?
* 饿汉
*/
public class SingleIntanceDemo {
//私有化构造
private SingleIntanceDemo(){}
private static SingleIntanceDemo instance = null;
public synchronized static SingleIntanceDemo getInstance(){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (instance==null) {
instance = new SingleIntanceDemo();
}
return instance;
}
}
(9) JDK5的Lock锁,我们之前造的所有的锁都没有手动释放锁
static Lock lock = new ReentrantLock();
枷锁:lock.lock();
释放锁:lock.unlock();
可以让我们明确的知道在哪里加锁和释放锁。
依然写一个卖票的demo,用lock枷锁释放锁,
为了保证我们创建的锁一定会被释放,用一下代码进行改进
try{....}finally{.....}
package com.edu_13;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyThread implements Runnable{
//定义100张票
int ticket = 100;
Object obj = new Object();
//创建一个锁
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try{
//加上锁,获取锁
lock.lock();
if (ticket>0) {
//考虑到实际的生活中,我们需要给每一个线程加入一定的延迟,模拟一下这种效果
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第:"+ticket--+"张票");
}
}finally{
//这里面的代码一定会被执行
//释放锁
lock.unlock();
}
}
}
}
package com.edu_13;
public class Test {
public static void main(String[] args) {
//创建MyThread对象
MyThread mt = new MyThread();
//创建三个窗口
Thread t1 = new Thread(mt);
Thread t2 = new Thread(mt);
Thread t3 = new Thread(mt);
//给每一个窗口设置姓名
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
//开启窗口进行售票
t1.start();
t2.start();
t3.start();
}
}
(10)死锁问题
同步嵌套,锁里面套了一个锁,出现同步嵌套
(简单介绍,要求大家以后写的时候需要注意)
package com.edu_14;
public class DieThread extends Thread{
boolean flag;
//提供一个有参构造
public DieThread(boolean flag){
this.flag = flag;
}
@Override
public void run() {
if (flag) {
synchronized (MyLock.objA) {
System.out.println("if"+"objA");
synchronized (MyLock.objB) {
System.out.println("if"+"objB");
}
}
}else {
synchronized (MyLock.objB) {
System.out.println("else"+"objB");
synchronized (MyLock.objA) {
System.out.println("else"+"objA");
}
}
}
}
}
package com.edu_14;
public abstract class MyLock {
//定义两个锁
public static final Object objA = new Object();
public static final Object objB = new Object();
}
package com.edu_14;
public class Test {
public static void main(String[] args) {
//创建两个线程,分别设置不同的布尔值
DieThread dt = new DieThread(true);
DieThread dt2 = new DieThread(false);
//开启两个线程
dt.start();
dt2.start();
}
}
(11)线程等待和唤醒机制(案例演示:waitThread,NotifyThread,MyLock,Test)
锁对象调用wait()锁对象调用notify()
注意:
wait和sleep的区别
线程等待,在等待的同时释放锁,而sleep()方法在执行的过程中是不会释放锁的
package com.edu_15;
public class Test {
public static void main(String[] args) {
//创建等待线程,让等待线程处于一个等待状态
WaitThread wt = new WaitThread();
wt.start();
//睡上5秒钟之后唤醒等待线程
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//创建唤醒线程对象
NotifyThread nt = new NotifyThread();
nt.start();
}
}
package com.edu_15;
public abstract class MyLock {
public static final Object obj = new Object();
}
package com.edu_15;
public class NotifyThread extends Thread{
@Override
public void run() {
synchronized (MyLock.obj) {
//唤醒等待线程
MyLock.obj.notify();//唤醒正在等待的线程,唤醒的等待线程的锁对象,必须和等待线程的锁对象一致
}
}
}
package com.edu_15;
public class WaitThread extends Thread{
@Override
public void run() {
synchronized (MyLock.obj) {
//让等待线程处于等待状态
try {
MyLock.obj.wait();//当线程处于等待状态的时候,线程就不会继续往下执行了
//线程在处于等待的时候,会释放掉自己手中的锁
//sleep()这个方法,在线程休息的时候会释放锁码?
//答:不会
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("我被唤醒了");
}
}