一、进程和线程的区别
(案例源代码:https://github.com/handsomelohas/javaCode)
在每一个进程上可以继续划分出若干个线程,那么线程的操作一定是要比进程更快的,所以多线程操作性能一定要超过多进程的操作。但是所有的线程都一定是要在进程的基础之上进行划分。所以进程一旦消失,那么线程也一定会消失。(线程永远要依附于进程存在)
Java多线程操作
在java中对于多线程实现一定要有一个线程的主类,而这个线程的主类往往是需要操作一些资源。但是对于这个多线程主类的实现是有一定要求:
1、继承Thread父类
2、实现Runnable接口(Callable接口)
二、继承Thread类实现多线程
在java.lang包中存在有Thread类,子类继承在Thread类之后需要覆写Thread类中的run()方法,那么这个方法就属于线程的主方法,定义:public void run()。
范例:实现线程的主体类:
package com.lohas.demo;
class MyThread extends Thread{//表示实现多线程
private String name;
public MyThread(String name){//线程的名字
this.name = name;
}
@Override
public void run(){//覆写run()方法,线程的主方法
//在线程的主类之中只是将内容输出10次
//(注意:所有的多线程的执行一定是并发完成的,即同一个时间段上会有多个线程线程交替执行)
//所以为了达到这样的目的,绝对不能够直接去调用run()方法,而是应该调用Thread类中的start()方法启动多线程:public void start()
for (int i = 0; i < 10; i++) {
System.out.println(this.name + ",i= " + i);
}
}
}
public class TestDemoo {
public static void main(String[] args) {
MyThread mt1 = new MyThread("线程A");
MyThread mt2 = new MyThread("线程B");
MyThread mt3 = new MyThread("线程C");
mt1.start();
mt2.start();
mt3.start();
}
}
运行的结果:
线程B,i= 0
线程A,i= 0
线程C,i= 0
线程A,i= 1
线程B,i= 1
线程A,i= 2
线程C,i= 1
线程A,i= 3
线程B,i= 2
线程A,i= 4
线程C,i= 2
线程A,i= 5
线程B,i= 3
线程A,i= 6
线程C,i= 3
线程A,i= 7
线程B,i= 4
线程A,i= 8
线程C,i= 4
线程C,i= 5
线程C,i= 6
线程C,i= 7
线程C,i= 8
线程C,i= 9
线程A,i= 9
线程B,i= 5
线程B,i= 6
线程B,i= 7
线程B,i= 8
线程B,i= 9
所有的线程都属于交替执行,本身是没固定的执行顺序的
public class TestDemoo {
public static void main(String[] args) {
MyThread mt1 = new MyThread("线程A");
MyThread mt2 = new MyThread("线程B");
MyThread mt3 = new MyThread("线程C");
mt1.run();
mt2.run();
mt3.run();
}
}
运行的结果:
线程A,i= 0
线程A,i= 1
线程A,i= 2
线程A,i= 3
线程A,i= 4
线程A,i= 5
线程A,i= 6
线程A,i= 7
线程A,i= 8
线程A,i= 9
线程B,i= 0
线程B,i= 1
线程B,i= 2
线程B,i= 3
线程B,i= 4
线程B,i= 5
线程B,i= 6
线程B,i= 7
线程B,i= 8
线程B,i= 9
线程C,i= 0
线程C,i= 1
线程C,i= 2
线程C,i= 3
线程C,i= 4
线程C,i= 5
线程C,i= 6
线程C,i= 7
线程C,i= 8
线程C,i= 9
结论:调用run()方法没有交替执行,按顺序执行
思考:为什么启动多线程不使用run()方法,而非要使用start()方法?
为了方便解释此问题,必须打开Thread类中的start源代码来观察(JDK源代码目录:C:\Java\jdk1.8.0_74\src.zip)。
public synchronized void start() {
if (threadStatus != 0)
throw newIllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();
现在的代码之中首先可以发现,方法会抛出一个异常:IllegalThreadStateException,使用throw声明,没有使用try...catch捕获处理,而之所以会出现这样的情况是因为此异常属于RuntimeException
IllegalThreadStateException的继承图
java.lang.Object
java.lang.Throwable
java.lang.Exception
java.lang.RuntimeException
java.lang.IllegalArgumentException
java.lang.IllegalThreadStateException
此异常指的是一个线程已经调用了start()方法后又重复执行了start()方法所造成的问题。
在调用start()方法里面发现会调用start0()方法,而start0()方法上使用了native 关键字定义,这个关键字指的是要调用那个本机的操作系统函数。
由于线程的启动需要牵扯到操作系统中资源的分配问题,所以具体的线程的启动应该要根据不同的操作系统有不同的实现,而JVM相当于根据系统定义的start0()方法来根据不同操作系统进行该方法的实现,,这样在多线程的start0()方法名称不改变。而不同的操作系统上有不同的实现。(类似接口的实现)
结论:只有Thread类的start()方法才能够进行操作系统资源的分配,所以启动多线程的方式永远就是调用Thread类的start()方法实现。
三、实现Runnable接口
继承Thread类会产生单继承的局限操作,所以现在最好的做法是利用接口来解决问题,于是就可以使用Runnable接口来完成操作。Runnable接口的定义结构:
@Functional Interface
public interface Runnable{
public void run();
}
此时的代码使用的是函数式的接口,可以利用Lamda表达式完成。
范例:按照正常思路实现多线程
packagecom.lohas.demo;
class MyRunnable implements Runnable{//表示实现多线程
private String name;
public MyRunnable(String name){//线程的名字
this.name = name;
}
@Override
public void run(){//覆写run()方法,线程的主方法
//在线程的主类之中只是将内容输出10次
//(注意:所有的多线程的执行一定是并发完成的,即同一个时间段上会有多个线程线程交替执行)
//所以为了达到这样的目的,绝对不能够直接去调用run()方法,而是应该调用Thread类中的start()方法启动多线程:public void start()
for (int i = 0; i < 10; i++) {
System.out.println(this.name +",i= " + i);
}
}
}
public class RunnableDemo {
public static void main(String[] args) {
MyRunnable mr1 = new MyRunnable("线程A");
MyRunnable mr2 = new MyRunnable("线程B");
MyRunnable mr3 = new MyRunnable("线程C");
new Thread(mr1).start();
new Thread(mr2).start();
new Thread(mr3).start();
}
}
如果要想启动多线程依靠只能够是Thread类中的start()方法,在之前继承Thread的时候可以直接将start()方法继承下来继续使用,但是现在实现的是Runnable接口,所以此方法没有了。
于是来观察Thread类中的构造方法:publicThread(Runnable target)。
很多时候为了方便实现,可能直接使用匿名内部类或者是Lamda表达式实现代码。
范例:匿名内部类
packagecom.lohas.demo;
public classAnonyRunnableDemo {
public static void main(String[] args) {
String name = "线程对象";
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++){
System.out.println(name +",i = " + i);
}
}
}).start();
}
}
范例:Lamda表达式
package com.lohas.demo;
public class LamdaRunnableDemo {
public static void main(String[] args) {
String name = "Lamda线程对象";
new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println(name +",i = " + i );
}
}).start();
}
}
只要给出的是函数式接口基本上就都可以使用Lamda表达式或者是方法引用。
对于多线程的两种实现模式:继承Thread类、实现Runnable接口,那么这两种模式本质上来讲,一定是使用Runnable接口,这样可以避免单继承局限,但是除了这样的使用原则之外,还需要清楚这两种实现方式的联系。
首先来观察Thread类的定义结构:publicclass Thread extends Object implements Runnable。
可以发现Thread类实现了Runnable接口。
通过类图描述关系可以发现,整个代码的操作中使用的就是一个代理设计模式的结构,但是与传统的代理设计还有一些差别。如果按照传统代理设计模式来讲,现在如果要想启动多线程理论应该是run()方法,但是实质上现在调用的是start()方法,所以名称不符合,之所以会这样主要是因为长期发展后产物,最早的时候设计模式就是个梦。
除了以上的继承关联之外还有一点区别:Runnable接口实现的多线程要比Thread类实现的多线程更方便的表示出数据共享的概念。
范例:希望有三个线程进行卖票---Thread实现
package com.lohas.demo;
class MySellThread extends Thread{//表示实现多线程
private int ticket = 5;
private String name;
public MySellThread(Stringname){//线程的名字
this.name = name;
}
@Override
public void run(){//覆写run()方法,线程的主方法
for (int i = 0; i <50; i++) {
if (this.ticket> 0 ){
System.out.println(name + "卖票,ticket = " +this.ticket--);
}
}
}
}
public class SellThreadDemo {
public static voidmain(String[] args) {
MySellThread mt1 = newMySellThread("线程A");
MySellThread mt2 = newMySellThread("线程B");
MySellThread mt3 = newMySellThread("线程C");
mt1.start();
mt2.start();
mt3.start();
}
}
运行结果:
线程B卖票,ticket = 5
线程A卖票,ticket = 5
线程C卖票,ticket = 5
线程A卖票,ticket = 4
线程B卖票,ticket = 4
线程A卖票,ticket = 3
线程C卖票,ticket = 4
线程A卖票,ticket = 2
线程B卖票,ticket = 3
线程A卖票,ticket = 1
线程C卖票,ticket = 3
线程B卖票,ticket = 2
线程C卖票,ticket = 2
线程B卖票,ticket = 1
线程C卖票,ticket = 1
发现现在的三个线程各自都是在卖着各自的票
范例:使用Runnable接口来实现多线程
package com.lohas.demo;
class MySellRunnable implements Runnable{//表示实现多线程
private int ticket = 5;
private String name;
public MySellRunnable(String name){//线程的名字
this.name = name;
}
@Override
public void run(){//覆写run()方法,线程的主方法
for (int i = 0; i < 50; i++) {
if (this.ticket > 0 ){
System.out.println(name +"卖票,ticket = " + this.ticket--);
}
}
}
}
public class SellRunnableDemo {
public static void main(String[] args) {
MySellRunnable mr = newMySellRunnable("线程");
new Thread(mr).start();
new Thread(mr).start();
new Thread(mr).start();
}
}
运行结果:
线程卖票,ticket = 5
线程卖票,ticket = 4
线程卖票,ticket = 5
线程卖票,ticket = 2
线程卖票,ticket = 3
线程卖票,ticket = 1
请解释多线程的两种实现方式以及区别
多线程需要一个线程的主类,这个类要么继承Thread类,要么实现Runnable接口
使用Runnable接口可以比Thread类更好的实现数据共享的操作,并且利用Runnable接口可以避免单继承局限问题。
五、实现Callable接口
从JDK1.5之后对于多线程的实现多了一个Callable接口,在这个接口里面比Runnable接口唯一的强大之处在于它可以返回执行结果。此接口定义在java.util.concurrent包中定义。
@FunctionalInterface
public interfaceCallable<V>{
public V call() throws Exception
}
这个泛型表示的是返回值类型。Call()方法就相当于run()方法。
(但是现在出现了一个问题,Thread类中并没有提供接收Callable接口的对象操作。所以出现在如何启动多线程问题)
首先来观察java.util.concurrent.FutureTask<V>类的定义结构:
java.lang.Object
java.util.concurrent.FutureTask<V>
All Implemented Interfaces:
Runnable, Future<V>, RunnableFuture<V>
范例:
package com.lohas.demo;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class MySellCallable implements Callable<String> {//表示实现多线程
private int ticket = 5;
private String name;
public MySellCallable(Stringname){//线程的名字
this.name = name;
}
@Override
public String call(){//覆写call()方法,线程的主方法
for (int i = 0; i <50; i++) {
if (this.ticket> 0 ){
System.out.println(name + "卖票,ticket = " +this.ticket--);
}
}
return "票卖完了";
}
}
public class CallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//但是现在出现了一个问题,Thread类中并没有提供接收Callable接口的对象操作。所以出现在如何启动多线程问题
MySellCallable mc =new MySellCallable("Call线程:");
FutureTask<String> futureTask = new FutureTask<>(mc);//取得执行结果
Thread thread = newThread(futureTask);
thread.start();
System.out.println("返回值 = [" + futureTask.get() + "]");//取得线程主方法的返回值
}
}
运行结果:
Call线程:卖票,ticket= 5
Call线程:卖票,ticket= 4
Call线程:卖票,ticket= 3
Call线程:卖票,ticket= 2
Call线程:卖票,ticket= 1
返回值 = [票卖完了]
总结:
Thread有单继承局限性所以不使用,但是所有的线程对象一定要通过Thread里中的start()方法启动。
六、线程的命名和取得操作、线程的休眠、线程的优先级
线程的所有操作方法几乎都在Thread类中定义好了。
6.1线程的命名和取得操作
从本质上来讲多线程的运行状态并不是固定的,所以来讲要想确定线程的执行,唯一的区别就在于线程的名称上。在起名的时候就应该尽可能避免重名,或者避免修改名称。
在Thread类中提供有如下的方法可以实现线程名称的操作:
构造方法:publicThread(Runnable target,String name)
设置名字:publicfinal String getName()
取得名字:publicfinal String getName()
既然线程的执行本身就不确定的状态,所以如果要取得线程名字的话,那么唯一能做的就是取得当前的线程名字,所以在Thread类里面提供有这样的方法:public static Thread currentThread()。
范例:线程的命令和取得
package com.lohas.demo;
class MyNameThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//System.out.println(Thread.currentThread().getId());
System.out.println(Thread.currentThread().getName() + ",i = "+ i);
}
}
}
public class ThreadNameDemo {
public static void main(String[] args) {
MyNameThread mnt = new MyNameThread();
new Thread(mnt,"线程A").start();
new Thread(mnt).start();
new Thread(mnt).start();
}
}
运行结果:
Thread-0,i = 0
Thread-0,i = 1
线程A,i = 0
Thread-0,i = 2
线程A,i = 1
Thread-0,i = 3
线程A,i = 2
Thread-0,i = 4
线程A,i = 3
线程A,i = 4
线程A,i = 5
Thread-1,i = 0
线程A,i = 6
线程A,i = 7
Thread-0,i = 5
线程A,i = 8
Thread-1,i = 1
线程A,i = 9
Thread-0,i = 6
Thread-1,i = 2
Thread-1,i = 3
Thread-1,i = 4
Thread-0,i = 7
Thread-1,i = 5
Thread-0,i = 8
Thread-1,i = 6
Thread-0,i = 9
Thread-1,i = 7
Thread-1,i = 8
Thread-1,i = 9
如果在设置线程对象时没有设置具体的名字,那么就采用一个默认的名字进行定义。
范例:观察以下代码
package com.lohas.demo;
class MyNameThread1 implements Runnable{
@Override
public void run() {
System.out.println("MyNameThread1线程类: " +Thread.currentThread().getName());
}
}
public class MyNameDemo1 {
public static voidmain(String[] args) {
MyNameThread1 mnt =new MyNameThread1();
newThread(mnt).start();//线程启动调用run()方法
mnt.run(); //直接通过对象调用run()方法
}
}
运行结果:
MyNameThread1线程类: main (mnt.run())
MyNameThread1线程类: Thread-0 (newThread(mnt).start())
问题:线程一定是依附于进程存在的,但是现在的进程在哪里呢?
每当使用java命令在JVM上解释某一个程序执行的时候,那么都会默认的启动一个JVM的进程,而主方法只是这进程中的一个线程,所以整个程序一直都跑在线程的运行机制上。
每一个JVM至少会启动两个人线程:主线程、GC线程。
6.2 线程的休眠
如果要想让某些线程延缓执行,那么就可以使用休眠的方式来进行处理,在Thread类里面提供如下休眠操作:
休眠方法:public staticvoid sleep(long millis) throws InterruptedException
(InterruptedException:中断异常)如果休眠的时间没到就停止休眠了,那么就会产生中断异常。
范例:观察休眠
packagecom.lohas.demo;
class MySleepThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ",i = "+ i);
}
}
}
public class ThreadSleepDemo {
public static void main(String[] args) {
MySleepThread mnt = newMySleepThread();
new Thread(mnt, "线程A").start();
new Thread(mnt, "线程B").start();
new Thread(mnt, "线程C").start();
}
}
运行结果:
线程A,i = 0
线程B,i = 0
线程C,i = 0
线程B,i = 1
线程C,i = 1
线程A,i = 1
线程A,i = 2
线程C,i = 2
线程B,i = 2
线程B,i = 3
线程A,i = 3
线程C,i = 3
线程A,i = 4
线程C,i = 4
线程B,i = 4
线程B,i = 5
线程C,i = 5
线程A,i = 5
线程A,i = 6
线程C,i = 6
线程B,i = 6
线程C,i = 7
线程A,i = 7
线程B,i = 7
线程B,i = 8
线程C,i = 8
线程A,i = 8
线程B,i = 9
线程C,i = 9
线程A,i = 9
以上的代码执行中感觉像是所有线程对象都同时休眠了。但是严格来讲不是同时,是有先后顺序的,只不过顺序小一点而已。
范例:
packagecom.lohas.demo;
class MySleepThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ",i = "+ i);
}
}
}
public class ThreadSleepDemo{
public static void main(String[] args)throws InterruptedException {
MySleepThread mnt = newMySleepThread();
// new Thread(mnt, "线程A").start();
// new Thread(mnt, "线程B").start();
// new Thread(mnt, "线程C").start();
//线程只能被其他线程中断,这里模仿中断
Thread t = new Thread(mnt, "线程A");
t.start();
Thread.sleep(2000);
t.interrupt();//中断
}
}
后续会使用休眠来进行线程的分析。
6.3 线程的优先级
从理论上来讲优先级越高的线程越有可能先执行。而在Thread类里面定义有以下的优先级的操作方法:
设置优先级:public finalvoid setPriority(int newPriority)
取得优先级:public finalint getPriority()
而对于优先级一共定义有三种:
最高优先级:public staticfinal int MAX_PRIORITY ----10
中等优先级:public staticfinal int NORM_PRIORITY ----5
最低优先级:public staticfinal int MIN_PRIORITY -----1
范例:
packagecom.lohas.demo;
class MyPriorityThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ",i = "+ i);
}
}
}
public class ThreadPriorityDemo {
public static void main(String[] args)throws InterruptedException {
MyPriorityThread mpt = newMyPriorityThread();
Thread t1 = new Thread(mpt, "线程A");
Thread t2 = new Thread(mpt, "线程B");
Thread t3 = new Thread(mpt, "线程C");
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
t3.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
t3.start();
}
}
运行结果:
线程C,i = 0
线程B,i = 0
线程A,i = 0
线程A,i = 1
线程C,i = 1
线程B,i = 1
线程A,i = 2
线程B,i = 2
线程C,i = 2
线程B,i = 3
线程C,i = 3
线程A,i = 3
线程A,i = 4
线程B,i = 4
线程C,i = 4
线程A,i = 5
线程C,i = 5
线程B,i = 5
线程A,i = 6
线程C,i = 6
线程B,i = 6
线程A,i = 7
线程B,i = 7
线程C,i = 7
线程A,i = 8
线程C,i = 8
线程B,i = 8
线程C,i = 9
线程B,i = 9
线程A,i = 9
可以看出理论上是A,但是有的是其他的
范例:
public class ThreadPriorityDemo{
public static void main(String[] args)throws InterruptedException {
System.out.println(Thread.currentThread().getPriority());
}
}
运行结果:
5
可以发现主线程属于一般优先级(中等优先级)。
总结:
1. 线程要有名字,Thread.currentThread取得当前线程。
2. 线程的休眠是有先后顺序的。
3. 理论上线程的优先级越高越有可能先执行。
七、线程同步
如果要想进行同步的操作,那么很明显就是多个线程需要访问同一资源。
范例:
package com.lohas.demo;
class MySynThread implements Runnable{
private int ticket = 5;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if(ticket > 0){
System.out.println(Thread.currentThread().getName() + ",剩余ticket=" + ticket--);
}
}
}
}
public class SynThreadDemo {
public static void main(String[] args) {
MySynThread mst = new MySynThread();
Thread t1 = new Thread(mst,"售票机A");
Thread t2 = new Thread(mst,"售票机B");
Thread t3 = new Thread(mst,"售票机C");
t1.start();
t2.start();
t3.start();
}
}
运行结果:
售票机A,剩余ticket= 5
售票机A,剩余ticket= 3
售票机B,剩余ticket= 4
售票机A,剩余ticket= 1
售票机C,剩余ticket= 2
范例:
package com.lohas.demo;
class MySynThread implements Runnable{
private int ticket = 5;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedExceptione) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ",剩余ticket=" + ticket--);
}
}
}
}
public class SynThreadDemo {
public static void main(String[] args) {
MySynThread mst = new MySynThread();
Thread t1 = new Thread(mst,"售票机A");
Thread t2 = new Thread(mst,"售票机B");
Thread t3 = new Thread(mst,"售票机C");
t1.start();
t2.start();
t3.start();
}
}
运行结果:
售票机A,剩余ticket= 4
售票机B,剩余ticket= 5
售票机C,剩余ticket= 5
售票机C,剩余ticket= 3
售票机B,剩余ticket= 2
售票机A,剩余ticket= 2
售票机C,剩余ticket= 1
售票机B,剩余ticket= -1
售票机A,剩余ticket= 0
上上面的范例也有这样的问题,只是加上休眠更容易出现方便分析线程同步。
此时就可以观察出程序的问题所在了。出现了负数
那么这样的操作就属于线程的不同步的操作,所以发现多个线程操作时必须要考虑到资源不同步的问题。
分析:整个的代码发现有一个逻辑的流程错误了。以上的程序中,将是否有票、延迟、卖票分为了三个部分。那么实际上每一个线程如果执行卖票的话,其他线程应该等待当前线程执行完毕后才可以进入。
如果要想在若干行代码实现锁这个概念,那么就需要通过使用同步代码块或者同步方法来解决。
1. 同步代码块
使用synchronized关键字定义的代码块就称为同步代码块,但是在进行同步的时候需要设置有一个同步对象,那么往往可以使用this同步当前对象。
范例:
package com.lohas.demo;
class MySynThread implements Runnable{
private int ticket = 20;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
synchronized (this) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ",剩余ticket=" + this.ticket--);
}
}
}
}
}
public class SynThreadDemo {
public static void main(String[] args) {
MySynThread mst = new MySynThread();
Thread t1 = new Thread(mst,"售票机A");
Thread t2 = new Thread(mst,"售票机B");
Thread t3 = new Thread(mst,"售票机C");
t1.start();
t2.start();
t3.start();
}
}
运行结果:
售票机A,剩余ticket= 20
售票机A,剩余ticket= 19
售票机A,剩余ticket= 18
售票机A,剩余ticket= 17
售票机A,剩余ticket= 16
售票机B,剩余ticket= 15
售票机B,剩余ticket= 14
售票机B,剩余ticket= 13
售票机B,剩余ticket= 12
售票机B,剩余ticket= 11
售票机B,剩余ticket= 10
售票机B,剩余ticket= 9
售票机B,剩余ticket= 8
售票机B,剩余ticket= 7
售票机B,剩余ticket= 6
售票机B,剩余ticket= 5
售票机B,剩余ticket= 4
售票机B,剩余ticket= 3
售票机B,剩余ticket= 2
售票机B,剩余ticket= 1
加入同步之后整个的代码执行的速度已经变慢了,而且不像没有同步的时候那样,多个线程会一起进入到方法之中。异步的执行速度要快于同步的执行速度,但是异步的操作属于非线程安全的操作,而同步操作属于线程安全的操作。
但是对于同步操作,除了用于代码块定义外,也可以在方法上定义同步操作。
范例:
package com.lohas.demo;
class MySynMethodThread implements Runnable{
private int ticket = 20;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
sale();
}
}
public synchronized void sale(){
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ",剩余ticket=" + this.ticket--);
}
}
}
public class SynMethodThreadDemo {
public static void main(String[] args) {
MySynMethodThread mst = newMySynMethodThread();
Thread t1 = new Thread(mst,"售票机A");
Thread t2 = new Thread(mst,"售票机B");
Thread t3 = new Thread(mst,"售票机C");
t1.start();
t2.start();
t3.start();
}
}
运行结果:
售票机A,剩余ticket= 20
售票机A,剩余ticket= 19
售票机C,剩余ticket= 18
售票机B,剩余ticket= 17
售票机B,剩余ticket= 16
售票机B,剩余ticket= 15
售票机B,剩余ticket= 14
售票机B,剩余ticket= 13
售票机B,剩余ticket= 12
售票机B,剩余ticket= 11
售票机B,剩余ticket= 10
售票机B,剩余ticket= 9
售票机B,剩余ticket= 8
售票机B,剩余ticket= 7
售票机B,剩余ticket= 6
售票机B,剩余ticket= 5
售票机B,剩余ticket= 4
售票机B,剩余ticket= 3
售票机B,剩余ticket= 2
售票机B,剩余ticket= 1
在多个线程访问同一资源时一定要考虑到数据的同步问题,同步就使用synchronized关键字。
死锁分析
很明显,死锁是一种不确定的状态,对于死锁的操作应该出现的越少越好,下面的代码只是一个死锁的演示,能否出现,代码不做任何的实际意义。
范例:
package com.lohas.demo;
class Woman {
//这里去掉synchronized能执行完成(一种)
public synchronized void say(Man man){
System.out.println("女人说:你给钱,我就过去。");
man.get();
}
//这里去掉synchronized能执行完成
public synchronized void get(){
System.out.println("女人拿到钱,就过去了。");
}
}
class Man {
//这里去掉synchronized能执行完成(另一种)
public synchronized void say(Woman woman){
System.out.println("男人说:你过来,我就给钱。");
woman.get();
}
//这里去掉synchronized能执行完成
public synchronized void get(){
System.out.println("男人艹到人,给钱了。");
}
}
public class WomanAndManDemo implements Runnable {
private Woman woman = new Woman();
private Man man = new Man();
//线程
public WomanAndManDemo(){
new Thread(this).start();
woman.say(man);
}
public static void main(String[] args) {
new WomanAndManDemo();
}
@Override
public void run() {
man.say(woman);
}
}
运行结果:
没去掉synchronized的话会死锁,如下图:
问题:请问多个线程访问同一资源部时可能带来什么问题?以及会产生什么样的附加问题
1. 多个线程访问同一资源时必须考虑同步,可以使用sychronized定义同步代码块或者同步方法。
2. 程序中如果出现过多的同步那么就会产生死锁。
总结:
如果看见了synchronized声明方法,一定要记住,这是一个同步方法,属于线程安全操作。