多线程的概念已经写过很多了,java的多线程的概念和之前讲解的 OC 与 python多线程相同,这里就不再赘述了.其基本原理也相同,只是方法有所区别.主要列举java中多线程的方法和使用.
一 : 线程创建的两种方式
-
继承方式
通过继承Thread 进行进行线程创建
首先创建一个类继承与Thread
重写 run
方法
//创建一个继承于Thread的子类
class SubThread extends Thread{
//重写Thread的run方法,方法内部实现此线程要完成的功能
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
调用
public class TestThread {
public static void main(String[] args) {
//创建一个子类的对象
SubThread st = new SubThread();
//调用线程的start() 启动此线程;调用相应的run()方法
st.start();
//不能通过Thread实现类对象的run()去启动一个线程
}
}
-
实现方式
创建一个实现Runnable
接口的类
class TZprint implements Runnable{
//实现接口的抽象方法
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println();
}
}
}
}
调用
public class TestThread2 {
public static void main(String[] args) {
//创建一个Runnable接口实现类对象
TZprint p = new TZprint();
//将此对象作为形参传递给Thread类的构造器中,创建Thread类的对象,
//此对象即为一个线程
Thread t = new Thread(p);
//调用start()方法,启动线程并执行run()
t.start();//启动线程,执行Thread对象生成时构造器形参的run方法
}
}
- 对比
联系 : 都变相的实现了 Runnable 接口
谁好 : 实现的方式优于继承的方式
优点 : 避免了java中单继承的局限性
: 如果多个线程要操作同一份资源,更适合用实现的方式
二 : 线程中常用方法
创建一个继承于Thread
的子类
class SubThread1 extends Thread {
// 2.重写Thread的run方法,方法内部实现此线程要完成的功能
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + i + Thread.currentThread().getPriority());
}
}
}
线程常用的方法
-
start()
启动线程并执行相应的run()方法
// 创建一个子类的对象
SubThread1 st = new SubThread1();
// 调用线程的start() 启动此线程;调用相应的run()方法
st.start();
-
run()
子线程要执行的代码放入run()方法中
注意 : 不能通过Thread实现类对象的run()去启动一个线程,要通过start()
方法去启动. -
currentThread()
静态的调取当前的线程 -
getName()
获取此线程的名字
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i + Thread.currentThread().getPriority());
}
-
setName()
设置此线程的名字
SubThread1 st = new SubThread1();
st.setName("test");
-
yield()
调用此方法的线程,释放当前CPU的执行权
Thread.currentThread().yield();
-
join()
在A线程中调用B线程的join()方法 表示 : 当执行到此方法,A线程停止执行 直至B线程执行完毕.A线程再接着join()之后的代码执行.
SubThread1 st = new SubThread1();
st.start();
for (int i = 0; i < 100; i++) {
if (i == 20) {
try {
st.join();
} catch (InterruptedException e) {
TODO Auto-generated catch block
e.printStackTrace();
}
}
}
-
isAlive()
判断当前线程是否还存活
SubThread1 st = new SubThread1();
st.start();
System.out.println(st.isAlive());
三 : 线程同步
常见的线程安全问题
实例-模拟火车站售票窗口,开启三个窗口售票,总票数为100张
class Window extends Thread {
static int ticket = 100;
public void run() {
while (true) {
if (ticket > 0) {
// try {
// Thread.currentThread().sleep(10);
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
System.out.println(Thread.currentThread().getName() + "售票,票号为:"
+ ticket--);
} else {
break;
}
}
}
}
调用
public class TestWindow {
public static void main(String[] args) {
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
}
}
分析 : 由于一个线程在操作共享数据过程中,未执行完毕,另外的线程参与进来,进入方法run 并且同时进入条件 ticket > 0 会造成错票现象,
解决办法 : 线程同步 的两种方式
必须让一个线程操作共享数据完毕以后,其它线程才有机会参与共享数据的操作。
-
方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码块(即为操作共享数据的代码)
}
1.共享数据:多个线程共同操作的同一个数据(变量)
2.同步监视器:由一个类的对象来充当。哪个线程获取此监视器,谁就执行大括号里被同步的代码。俗称:锁
要求:所有的线程必须共用同一把锁!
优化购票
class Window implements Runnable {
int ticket = 100;// 共享数据
// Object obj = new Object();
public void run() {
// Animal a = new Animal();//局部变量
while (true) {
synchronized (this) {//this表示当前对象,本题中即为w
if (ticket > 0) {
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "售票,票号为:" + ticket--);
}
}
}
}
}
public class TestWindow2 {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start()
}
}
注:在实现的方式中,考虑同步的话,可以使用this来充当锁。但是在继承的方式中,慎用this!
错误示范
解析 : 在此错误示例中,同步代码块中的锁,this
是 当前实例后的对象,相当于给每个 窗口 创建了一把锁,此时并不能起到互相约束的作用,此锁就变得没有意义.
class Window extends Thread {
static int ticket = 100;
static Object obj = new Object();
public void run() {
while (true) {
synchronized (this) {//在本问题中,this表示:w1,w2,w3
if (ticket > 0) {
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "售票,票号为:" + ticket--);
}
}
}
}
}
public static void main(String[] args) {
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
}
}
改正 : 把继承Thread
的window
中的同步代码块中的
this
换成obj
-
方式二:同步方法
将操作共享数据的方法声明为synchronized
。即此方法为同步方法,能够保证当其中一个线程执行
此方法时,其它线程在外等待直至此线程执行完此方法。
同步方法的锁:this
lass Window implements Runnable {
int ticket = 100;// 共享数据
public void run() {
while (true) {
show();
}
}
public synchronized void show() {
if (ticket > 0) {
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票,票号为:"
+ ticket--);
}
}
}
public class TestWindow4 {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
注意 : 同步方法里面的锁默认是 this
所以对于继承方式的线程起不到同步的作用.
线程的同步的弊端:由于同一个时间只能有一个线程访问共享数据,效率变低了,但这并能阻止我们去在必要的时候保持线程的安全性.
四 : 死锁
死锁的问题:处理线程同步时容易出现,不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
示例分析
public class TestDeadLock {
static StringBuffer sb1 = new StringBuffer();
static StringBuffer sb2 = new StringBuffer();
public static void main(String[] args) {
//线程A
new Thread() {
public void run() {
synchronized (sb1) {
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
sb1.append("A");
synchronized (sb2) {
sb2.append("B");
System.out.println(sb1);
System.out.println(sb2);
}
}
}
}.start();
//线程B
new Thread() {
public void run() {
synchronized (sb2) {
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
sb1.append("C");
synchronized (sb1) {
sb2.append("D");
System.out.println(sb1);
System.out.println(sb2);
}
}
}
}.start();
}
}
分析 :
在执行线程A (看代码注释)过程中 首先 sb1
为锁 执行同步代码块->sb1.append("A");
,拼接"A"后又以'sb2'为锁 执行sb2.append("B"); System.out.println(sb1); System.out.println(sb2);
同理在执行线程B先以 sb2
为锁头,再以sb1
为锁头 ,当然此时A 线程和B线程相当于同时进行,虽有先后顺序.
在A线程中以sb2
为锁头时 要等线程B用完,但要先执行完线程B 释放'sb2'这把锁,必须要等线程A中sb1
这把锁头用完,但是要想释放sb1
必须要等同步方法中以'sb2'为锁头时的同步方法执行完...无线循环.这就是死锁.
五 : 线程通信
关键字
-
wait()
:一旦一个线程执行到wait(),就释放当前的锁。 -
notify()
/notifyAll()
:唤醒wait的一个或所有的线程
示例
使用两个线程打印 1-100. 线程1, 线程2 交替打印
class PrintNum implements Runnable {
int num = 1;
Object obj = new Object();
public void run() {
while (true) {
synchronized (obj) {
obj.notify();
if (num <= 100) {
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":"
+ num);
num++;
} else {
break;
}
try {
obj.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
public class TestCommunication {
public static void main(String[] args) {
PrintNum p = new PrintNum();
Thread t1 = new Thread(p);
Thread t2 = new Thread(p);
t1.setName("甲");
t2.setName("乙");
t1.start();
t2.start();
}
}
网友评论