一:多线程的一些概念!
class hello extends Thread {
public hello() {
}
public hello(String name) {
this.name = name;
}
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + "运行 " + i);
}
}
public static void main(String[] args) {
hello h1=new hello("A");
hello h2=new hello("B");
h1.start();//这里千万不要直接调用run()方法,否则就是一个普通的run方法了
h2.start();
}
private String name;
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0 || this != me)
throw new IllegalThreadStateException();
group.add(this);
start0();
if (stopBeforeStart) {
stop0(throwableFromStop);
}
}
private native void start0();
说明此处调用的是start0()。并且这个这个方法用了native关键字,次关键字表示调用本地操作系统的函数。因为多线程的实现需要本地操作系统的支持。
/**
* @author chen 实现Runnable接口
* */
class hello implements Runnable {
public hello() {
}
public hello(String name) {
this.name = name;
}
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + "运行 " + i);
}
}
public static void main(String[] args) {
hello h1=new hello("线程A");
Thread demo= new Thread(h1);
hello h2=new hello("线程B");
Thread demo1=new Thread(h2);
demo.start();
demo1.start();
}
private String name;
}
这两种方式的区别,我们到底该用那种方式去玩“多线程”?
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
class hello extends Thread {
public void run() {
for (int i = 0; i < 7; i++) {
if (count > 0) {
System.out.println("count= " + count--);
}
}
}
public static void main(String[] args) {
hello h1 = new hello();
hello h2 = new hello();
hello h3 = new hello();
h1.start();
h2.start();
h3.start();
}
private int count = 5;
}
打印结果:
count= 5
count= 4
count= 3
count= 2
count= 1
count= 5
count= 4
count= 3
count= 2
count= 1
count= 5
count= 4
count= 3
count= 2
count= 1
每个线程的资源“count”,都是独立的。class MyThread implements Runnable{
private int ticket = 5; //5张票
public void run() {
for (int i=0; i<=20; i++) {
if (this.ticket > 0) {
System.out.println(Thread.currentThread().getName()+ "正在卖票"+this.ticket--);
}
}
}
}
public class lzwCode {
public static void main(String [] args) {
MyThread my = new MyThread();
new Thread(my, "1号窗口").start();
new Thread(my, "2号窗口").start();
new Thread(my, "3号窗口").start();
}
}
打印结果:
count= 5
count= 4
count= 3
count= 2
count= 1
总结一下:
实现Runnable接口比继承Thread类所具有的优势:
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。
三:“线程安全”问题!
例子:一个用于演示线程安全的例子!
package com.ThreadDemo.bean;
/**
* 用于演示线程安全的例子
* <p/>
* <p/>
* 以下的代码,在多线程的情况下,是可能出现线程安全的情况的。
* <p/>
* 现象:order可能会打印出“负数”的情况
* <p/>
* 我们已经在程序中加了条件if(order > 0),为何还是会出现负数的情况呢?
* 原因:由于我们在“测试代码”中,我们写成“Quan quan = new Quan”,然后新建多个Thread,传入这个对象,然后开启多线程程序的方式!
* 由于quan这个对象是同一个对象,所以下面的多个线程共享的是同一份order变量,
* <p/>
* 当多条语句在操作同一个线程共享数据的时候,一个线程对多条语句只执行了一部分,还没结束的时候,另一个线程进入了此代码区域,进行下一次执行,导致共享数据发生错误!
* <p/>
* <p/>
* 解释:比如这个例子中,为何会出现负数?
* 比如Thread0,在执行run方法中的语句的时候,当此时order为1了,执行到Thread.sleep()方法的时候,这个线程的“执行权”,被Thread1抢过去了,Thread1执行run方法中的代码的时候,因为这个时候Thread0
* 还没有执行代码order--,所有Thread1在执行run方法中的代码的时候if(order > 0)是正确的,顺利进入了代码块!并且顺利执行了order--,那么此时order变成了0.
* 过了一会儿,Thread0线程被唤醒了,恢复了执行权,执行了刚刚还没结束的代码,order--,那么此时order就变成了-1,那么就出现了所谓的“线程安全”的问题。
* <p/>
* <p/>
* 什么情况下会出现线程安全的问题?
* 1 多线程的情况下
* 2 多线程共享同一份数据的时候
* 3 多线程执行的run方法中的代码有“多条语句”,可能出现某个线程没有完全执行完所有语句的情况下,被另外的线程抢走执行权的情况!
* <p/>
* <p/>
* 解决“线程安全”隐患的方法!
* 对多条语句执行共享数据的代码,在某一个时间段,执行某一个线程执行,这多条语句,并且只有都执行完了这些语句,下一个线程才被允许进行这多条语句的执行。在前一个线程
* 执行这多条语句的过程中,另一个线程即使获得了“执行权”,也只能暂时等待在哪里,当前面的线程全部执行完“多条语句”后,才能执行!也就是给这“多条语句”加上synchronized关键字!
*
* 使用synchronized关键字,同步时的条件:
* 1必须是多线程,也就是两个或者两个以上的线程去执行sybchronized关键字中的代码
* 2这多个线程必须使用的是同一把“锁”,如果是不同的锁,它们之间是不会达到“互斥”的效果的
*
* 使用synchronized的不足之处:多线程,在每次执行synchronized中的代码的时候,每次都需要判断参数中的“锁”对象,所以一定程度上会消耗一定的系统资源!
* <p/>
* <p/>
* 注意点:下面的多线程的程序使用的实现Runnable的方式实现多线程的,如果我们直接继承Thread,然后开启多线程的话,
* 比如Quan extend Thread,Quan quan = new Quan(),Quan quan1 = new Quan(),Quan quan2 = new Quan()
* 然后开启线程的话,由于这里我们new了多个Quan对象,每个Quan对象都拥有各自的独立的一份“order”变量,那么就不会出现上述的“线程安全”的问题!
* <p/>
* <p/>
* User: OF895
* Date: 14-11-12
* Time: 下午11:52
*/
public class Quan implements Runnable {
private int order = 100;
@Override
public void run() {
while (true) {
//这里的quan.class可以看做一把“锁”,持有该“锁”的线程,可以进行代码块中进行执行,当前不持有该“锁”的线程
//即使获得了“执行权”,也无法进入“同步代码块”中
synchronized (Quan.class) {
if (order > 0) {
try {
//让当前线程睡10ms
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "........." + order--);
}
}
}
}
public static void main(String[] args) {
Quan quan = new Quan();
Thread t1 = new Thread(quan);
Thread t2 = new Thread(quan);
Thread t3 = new Thread(quan);
Thread t4 = new Thread(quan);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
打印结果:
Thread-0.........100
Thread-0.........99
Thread-2.........98
Thread-1.........97
Thread-1.........96
Thread-1.........95
Thread-3.........94
Thread-3.........93
Thread-3.........92
Thread-1.........91
Thread-2.........90
Thread-0.........89
Thread-2.........88
Thread-2.........87
Thread-2.........86
Thread-1.........85
Thread-1.........84
Thread-3.........83
Thread-1.........82
Thread-1.........81
Thread-2.........80
Thread-0.........79
Thread-2.........78
Thread-1.........77
Thread-3.........76
Thread-3.........75
Thread-3.........74
Thread-1.........73
Thread-1.........72
Thread-2.........71
Thread-0.........70
Thread-0.........69
Thread-0.........68
Thread-2.........67
Thread-1.........66
Thread-1.........65
Thread-3.........64
Thread-1.........63
Thread-2.........62
Thread-0.........61
Thread-2.........60
Thread-1.........59
Thread-3.........58
Thread-1.........57
Thread-2.........56
Thread-0.........55
Thread-2.........54
Thread-1.........53
Thread-3.........52
Thread-1.........51
Thread-1.........50
Thread-2.........49
Thread-2.........48
Thread-0.........47
Thread-0.........46
Thread-2.........45
Thread-2.........44
Thread-2.........43
Thread-1.........42
Thread-3.........41
Thread-1.........40
Thread-1.........39
Thread-2.........38
Thread-0.........37
Thread-0.........36
Thread-0.........35
Thread-0.........34
Thread-2.........33
Thread-2.........32
Thread-2.........31
Thread-1.........30
Thread-3.........29
Thread-1.........28
Thread-2.........27
Thread-0.........26
Thread-0.........25
Thread-2.........24
Thread-1.........23
Thread-3.........22
Thread-3.........21
Thread-1.........20
Thread-2.........19
Thread-2.........18
Thread-0.........17
Thread-2.........16
Thread-1.........15
Thread-3.........14
Thread-1.........13
Thread-2.........12
Thread-0.........11
Thread-2.........10
Thread-1.........9
Thread-3.........8
Thread-1.........7
Thread-2.........6
Thread-0.........5
Thread-2.........4
Thread-1.........3
Thread-3.........2
Thread-1.........1
这样就实现了多个线程共享这100张券的情况,而不会出现小quan的数据小于0的情况!
四:我们如何去判断多线程的程序是否有线程安全的问题呢?
package com.ThreadDemo;
/**
* 目的:该程序是否有安全问题,如果有,如何解决?
* <p/>
* <p/>
* 如何判断一个程序是否有“线程安全”的问题?
* <p/>
* 1明确哪些代码是多线程运行的代码
* 2明确哪些是多线程的“共享数据”
* 3明确多线程运行代码中,哪些是“操作”共享数据的。
*
*
*
* 同步有两种表现形式:
* 1同步代码块
* 2同步函数,同步函数使用的”锁“是this
* <p/>
* <p/>
* User: OF895
* Date: 14-11-14
* Time: 下午11:53
*/
public class BankDemo {
public static void main(String[] args) {
Cus c = new Cus();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
class Cus implements Runnable {
private Bank b = new Bank();
@Override
public void run() {
for (int x = 0; x < 3; x++) { //这里的“x”,就不是“共享变量”了,因为每个线程都会有独自的”一份X变量“,这个是”局部变量“
b.add(100); //这里是线程执行的地方,但是真正的“多线程”执行的代码在Bank类的add方法中。
}
}
}
class Bank {
private int sum; //这个“成员变量”,那么肯定是共享数据
Object obj = new Object();
public void add(int n) {
synchronized (obj) {
sum = sum + n; //这个是“多线程代码”中,明确操作“共享数据的” ,这里有多条语句操作共享数据,那么就可能发生“线程安全”的问题
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sum = " + sum);//这个是“多线程代码”中,明确操作“共享数据的” ,这里有多条语句操作共享数据,那么就可能发生“线程安全”的问题
}
}
}
//class Bank {
// private int sum; //这个“成员变量”,那么肯定是共享数据
// Object obj = new Object();
//
// public synchronized void add(int n) { //这里的写法是”同步函数“的写法,和上面的”同步代码块“给区别开来,同步函数使用的锁是”this“
// sum = sum + n; //这个是“多线程代码”中,明确操作“共享数据的” ,这里有多条语句操作共享数据,那么就可能发生“线程安全”的问题
// try {
// Thread.sleep(10);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println("sum = " + sum);//这个是“多线程代码”中,明确操作“共享数据的” ,这里有多条语句操作共享数据,那么就可能发生“线程安全”的问题
// }
//}
五:拓展知识,单例模式有两种,饱汉式和懒汉式(这个在多线程的程序下可能会存在“线程安全的”问题)
饱汉式(这中没有线程安全的问题):
package com.ThreadDemo;
/**
* 饱汉式单列模式
* <p/>
* User: OF895
* Date: 14-11-16
* Time: 下午11:23
*/
public class SingleDemo1 {
public static void main(String[] args) {
Single s = Single.getInstance();
}
}
class Single {
//构造方法设置为private的,这样在类外就无法通过new创建类的对象了
private Single() {
}
private static final Single s = new Single();
//提供一个方法供外部调用,获得该类的唯一的对象
public static Single getInstance() {
return s;
}
}
懒汉式(这种在多线程的情况下有线程安全的问题):
package com.ThreadDemo;
/**
* "懒汉式"单列模式
* User: OF895
* Date: 14-11-16
* Time: 下午11:28
*/
public class SingleDemo2 {
}
class Singleton {
private Singleton() {
}
//这里的Singleton s是一个“共享数据”
private static Singleton s = null;
public static Singleton getInstance() {
//只有s==null的情况下,才返回s,这就是所谓的“懒汉式”,但是这个在多线程的情况下,可能就不能保证是得到的singleton对象是“同一个对象”了
//这里有多条语句在操作这个“共享数据s”,一条语句是判断s是否为null,第二条语句生成一个Singleton对象赋值给s,那么在多线程的情况下就会有线程安全的问题,需要加synchronized代码块。
synchronized (Singleton.class){ //因为这个方法是个静态方法,所以我们的锁不能用this,需要使用Singleton.class
if (s == null) {
s = new Singleton();
}
}
return s;
}
}
六:“同步函数”的例子:
package com.ThreadDemo;
/**
* 演示一下“同步函数”的例子
* <p/>
* 同步函数用的是哪一个“锁”呢?
* 函数需要被对象调用,那么函数都有一个所属对象引用,就是this
* <p/>
* 静态同步函数使用的锁是该方法所在的类的“class”,字节码文件,因为这个每个类只有一份,所以在静态方法上面加上这个synchronized关键字,那么也是线程不安全的
* 因为多个线程之间,他们使用的是同一个“锁”对象,那么就“锁”不了了,达不到安全的目的了!
* User: OF895
* Date: 14-11-16
* Time: 下午10:06
*/
public class SynchronizedMethodDemo {
public static void main(String[] args) {
Thread t1 = new Thread(new Book());
Thread t2 = new Thread(new Book());
Thread t3 = new Thread(new Book());
t1.start();
t2.start();
t3.start();
}
}
class Book implements Runnable {
private int num = 1000;
@Override
//千万不要把“synchronized”关键字加到这个run方法上,否则的话,会导致“只有一个”线程在启动执行代码,下一个线程无法进行到run方法中,也就无法执行run方法中的代码了!
public void run() {
show();
}
//同步函数,等同于上面的例子中的“同步代码块”的使用,也是为了解决“线程安全”的问题的,锁是“调用的对象”,也即是this
public synchronized void show() {
if (num > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
System.out.println(Thread.currentThread().getName() + "...........sale: " + num--);
}
}
}
七:如何避免“线程间通信时候”可能造成的线程通信的问题。
package com.ThreadDemo;
/**
* 如何避免“线程间通信”时,可能造成的“线程安全”问题
* <p/>
* <p/>
* 解释:这是一个“线程间”通信的小例子
* Input线程如果以奇数和偶数为标识,不停地向“Resource”中的name和sex“赋不同的值”,而Output不停的从中“取值”
* 这里有两个地方需要注意:
* <p/>
* 1 在Output类中的run方法中,虽然只有“一句”打印的语句,但是它是一个独立的线程,如果不添加synchronized关键字的话,如果Input线程set好了name之后,暂时休眠了,这时候Output线程获得了“执行权”,那么就会打印出 “mike 女”或者“丽丽 man”这样的“线程不安全”(数据被混淆了)的数据
* <p/>
* 2 因为我们分析了上面出现线程不安全的原因,就是因为两个线程Input和Output他们虽然是两个线程,但是他们对同一个共享数据Resource r是有不同的操作动作的,所以我们需要
* 都给他们加上“同步”,并且他们的“锁”对象必须是“一样的”(同一把锁),这样我们才能够保证打印出的永远是“mike man”,"丽丽 女",这样的“安全”的数据。
* <p/>
* <p/>
* <p/>
* User: OF895
* Date: 14-11-17
* Time: 下午10:21
*/
public class NotifyThread {
public static void main(String[] args) {
Resource r = new Resource();
Thread t1 = new Thread(new Input(r));
Thread t2 = new Thread(new Output(r));
t1.start();
t2.start();
}
}
class Resource {
String name;
String sex;
}
class Input implements Runnable {
private Resource r;
Input(Resource r) {
this.r = r;
}
@Override
public void run() {
int x = 0;
while (true) {
synchronized (r) {
if (x == 0) {
r.name = "mike";
r.sex = "man";
} else {
r.name = "丽丽";
r.sex = "女";
}
x = (x + 1) % 2;
}
}
}
}
class Output implements Runnable {
private Resource r;
Output(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true) {
//这里因为和上面的线程操作的是同一个“共享数据”,所以我们也需要加上synchronized关键字
//并且使用的“锁”对象,必须和Input中的“锁”对象保持一致
synchronized (r) {
System.out.println(r.name + ".........." + r.sex);
}
}
}
}