线程、同步
1.1 多线程原理
代码如下:
package Demo1;
public class Demo01MainTherad {
public static void main(String[] args) {
proson p1 = new proson(“湫”);
p1.run();
proson p2 = new proson("鲲");
p2.run();
}
}
package Demo1;
public class proson {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public proson(String name) {
this.name = name;
}
public proson() {
super();
// TODO 自动生成的构造函数存根
}
public void run() {
for (int i =0;i<20;i++) {
System.out.println(name+"");
}
}
}
结果:
湫湫湫湫湫湫湫湫湫湫湫湫湫湫湫湫湫湫湫湫
鲲鲲鲲鲲鲲鲲鲲鲲鲲鲲鲲鲲鲲鲲鲲鲲鲲鲲鲲
1.2Thread类
在上一天内容中我们已经可以完成最基本的线程开启,那么在我们完成操作过程中用到了 java.lang.Thread 类, API中该类中定义了有关线程的一些方法,具体如下: 构造方法:
public Thread() :分配一个新的线程对象。 public Thread(String name) :分配一个指定名字的新的线程对象。 public Thread(Runnable target) :分配一个带有指定目标新的线程对象。 public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。 常用方法: public String getName() :获取当前线程名称。 public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。 public void run() :此线程要执行的任务在此处定义代码。 public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。 public static Thread currentThread() :返回对当前正在执行的线程对象的引用。 翻阅API后得知创建线程的方式总共有两种,一种是继承Thread类方式,一种是实现Runnable接口方式,方式一我 们上一天已经完成,接下来讲解方式二实现的方式。
1.3 创建线程方式
二 采用 java.lang.Runnable 也是非常常见的一种,我们只需要重写run方法即可。 步骤如下: 1. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。 2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正 的线程对象。 3. 调用线程对象的start()方法来启动线程。 代码如下:
package Demo1;
/*
-
java.long.Thread
-
实现步骤:
-
1.创建一个Thread类的子类
-
2.在Thread子类中重写run的方法
-
3、创建Thread子类对象
-
4、调用Thread子类方法start启动线程,run
-
*/
public class Demo01Thread {
public static void main(String[] args) {
//3.创建thread的子类的对象
MyThread mt =new MyThread();//4.调用start方法 mt.start(); for (int i =0;i<20;i++) { System.out.println("主线程:"+i); if(i==10) { System.out.println(0/0); } } }
}
package Demo1;
/*
-
创建Thread子类
-
*/
public class MyThread extends Thread {
//2.重写run
public void run() {for (int i =0;i<20;i++) { System.out.println("子线程:"+i); }
}
}
主线程:0
主线程:1
子线程:0
主线程:2
子线程:1
主线程:3
子线程:2
主线程:4
子线程:3
主线程:5
子线程:4
主线程:6
子线程:5
主线程:7
子线程:6
主线程:8
子线程:7
主线程:9
子线程:8
主线程:10
子线程:9
子线程:10
子线程:11
子线程:12
Exception in thread “main” 子线程:13
子线程:14
子线程:15
子线程:16
子线程:17
java.lang.ArithmeticException: / by zero
子线程:18
子线程:19
1.4 Thread和Runnable的区别
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
总结: 实现Runnable接口比继承Thread类所具有的优势: 1. 适合多个相同的程序代码的线程去共享同一个资源。 2. 可以避免java中的单继承的局限性。 3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。 4. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。 扩充:在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用 java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实在就是在操作系统中启动了一个进 程。
Thread
package demo02;
/*
-
java.long.Thread
-
实现步骤:
-
1.创建一个Thread类的子类
-
2.在Thread子类中重写run的方法
-
3.创建Thread子类对象
-
4.调用Thread类中方法start启动线程,执行run方法
-
*/
public class Demo01Thread {
public static void main(String[] args) {
//3.创建thread的子类的对象
MyThread mt =new MyThread();//4.调用start方法 mt.start(); new MyThread().start();//线程 1 new MyThread().start();//线程 3 new MyThread().start();//线程 2 System.out.println("main:"+Thread.currentThread().getName()); } }
public class MyThreadName extends Thread {
public MyThreadName() {}
public MyThreadName(String name) {
super(name);//把线程的名字传递给父类,让父类Thread给子线程起名字
}
public void run() {
//String name=getName();
//System.out.println("run"+name);
System.out.println("子:"+Thread.currentThread().getName());
}
}
结果
main:main
子Thread-1
子Thread-3
子Thread-0
子Thread-2
Runnable
package demo02;
public class Demo02MyThreadSetName {
public static void main(String[] args) {
MyThreadName mt =new MyThreadName(“小钱”);
mt.start();
new MyThreadName("旺财").start();
}
}
package demo02;
/*
-
创建Thread子类
-
*/
public class MyThread extends Thread {
//2.重写run
public void run() {//String name=getName();
//System.out.println(“run”+name);
System.out.println(“子”+Thread.currentThread().getName());
}
}
结果
子:小钱
子:旺财
2.Timed Waiting(计时等待)
Timed Waiting在API中的描述为:一个正在限时等待另一个线程执行一个(唤醒)动作的线程处于这一状态。单独 的去理解这句话,真是玄之又玄,其实我们在之前的操作中已经接触过这个状态了,在哪里呢? 在我们写卖票的案例中,为了减少线程执行太快,现象不明显等问题,我们在run方法中添加了sleep语句,这样就 强制当前正在执行的线程休眠(暂停执行),以“减慢线程”。 其实当我们调用了sleep方法之后,当前执行的线程就进入到“休眠状态”,其实就是所谓的Timed Waiting(计时等 待),那么我们通过一个案例加深对该状态的一个理解。
实现一个计数器,计数到60,在每个数字之间暂停1秒,
package Demo3;
public class Dem03Sleep {
public static void main(String[] args) {
for(int i=0;i<60;i++) {
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
}
通过案例可以发现,sleep方法的使用还是很简单的。我们需要记住下面几点: 1. 进入 TIMED_WAITING 状态的一种常见情形是调用的 sleep 方法,单独的线程也可以调用,不一定非要有协 作关系。 2. 为了让其他线程有机会执行,可以将Thread.sleep()的调用放线程run()之内。这样才能保证该线程执行过程 中会睡眠 3. sleep与锁无关,线程睡眠到期自动苏醒,并返回到Runnable(可运行)状态。 小提示:sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就 开始立刻执行。
3.Runnable和runnable
package Demo04;
public class Demo04Runnable {
public static void main(String[] args) {
//3.创建一个Runnble 接口的实现类 对象
Runnablelmpl run =new Runnablelmpl();
//4.创建对象,构造方法中传递Runnable接口实现类对象
//Thread t=new Thread(run);
//5.调用Thread类start方法,启动子线程
Thread t=new Thread(new Runalelmpl2());
t.start();
for(int i=0;i<20;i++) {
System.out.println(Thread.currentThread().getName()+"–>"+i);
}
}
}
package Demo04;
//1.创建一个runnable接口的实现类
public class Runnablelmpl implements Runnable {
@Override
public void run() {
// TODO 自动生成的方法存根
for(int i=0;i<20;i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
package Demo04;
//创建一个runnable接口实现类
public class Runalelmpl2 implements Runnable {
//实现类中重写runnable中run方法,设置线程任务
@Override
public void run() {
// TODO 自动生成的方法存根
for(int i=0;i<20;i++) {
System.out.println(“HelloWorld–>”+i);
}
}
}
- 线程安全
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样 的,而且其他的变量的值也和预期的是一样的,就是线程安全的。 我们通过一个案例,演示线程的安全问题: 电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是 “葫芦娃大战奥特曼”,本次电影的座位共100个 (本场电影只能卖100张票)。 我们来模拟电影院的售票窗口,实现多个窗口同时卖 “葫芦娃大战奥特曼”这场电影票(多个窗口一起卖这100张票) 需要窗口,采用线程对象来模拟;需要票,Runnable接口子类来模拟 模拟票:
package demo05;
public class Demo01Ticket {
public static void main(String[] args) {
Runnablempl run =new Runnablempl();
Thread t0= new Thread(run);
Thread t1= new Thread(run);
Thread t2= new Thread(run);
t0.start();
t1.start();
t2.start();
}
}
package demo05;
/*
- 解决线程安全问题的方案:使用同步代码块
- 格式:
- synchronized(锁对象) {
-
可能会出现的安全代码(是因为访问了共享的数据)
- }
- 注意:
- 1、通过代码块中的锁对象,
- 2、但是
- 3、
*/
public class Runnablempl implements Runnable {
private int ticket =100;
Object obj= new Object();
public void run() {
// TODO 自动生成的方法存根
while(true) {
//synchronized(obj) {
if(ticket>0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticket+"张票");
ticket--;
}
//}
}
}
}
在这里插入图片描述
package demo05;
public class Demo01Ticket {
public static void main(String[] args) {
Runnablempl run =new Runnablempl();
Thread t0= new Thread(run);
Thread t1= new Thread(run);
Thread t2= new Thread(run);
t0.start();
t1.start();
t2.start();
}
}
package demo05;
/*
- 解决线程安全问题的方案:使用同步代码块
- 格式:
- synchronized(锁对象) {
-
可能会出现的安全代码(是因为访问了共享的数据)
- }
- 注意:
- 1、通过代码块中的锁对象,
- 2、但是
- 3、
*/
public class Runnablempl implements Runnable {
private int ticket =100;
Object obj= new Object();
public void run() {
// TODO 自动生成的方法存根
while(true) {
synchronized(obj) {
if(ticket>0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticket+"张票");
ticket--;
}
}
}
}
}
2020080605044