java多线程
文章主要内容
- 线程的创建方式
- 线程的几种状态
- 线程的安全性
1. 线程的创建方式
1、继承Thread类:
Thread类其实是实现了Runnable接口的一个实例,继承Thread类后需要重写run方法并通过start方法启动线程。
继承Thread类耦合性太强了,因为java只能单继承,如果一个类继承了另外一个父类,此时要实现多线程就不能通过继承Thread的类实现,所以不利于扩展。
public class ThreadDemo1 extends Thread {
//重写run方法
@Override
public void run() {
for(int i = 1;i <= 100;i++){
//System.out.println(super.getName() + ":" + i);
}
}
public static void main(String[] args){
//通过自定义线程类创建线程
//一个线程对象对应一个线程
ThreadDemo1 t1 = new ThreadDemo1();
ThreadDemo1 t2 = new ThreadDemo1();
//线程改名
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
2、实现Runnable接口
通过实现Runnable接口并重写run方法,并把Runnable实例传给Thread对象,Thread的start方法调用run方法再通过调用Runnable实例的run方法启动线程。
public class RunnableDemo implements Runnable {
//重写run方法
@Override
public void run() {
for(int i = 1;i <= 100; i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void main(String[] args) {
//创建RunnableDemo对象,将其作为参数传入到Thread对象
RunnableDemo r = new RunnableDemo();
new Thread(r,"t1").start();;
new Thread(r,"t2").start();
}
}
2.线程的几种状态
1、new:表示线程刚刚创建,还没开始启动。
2、runnable:表示线程已经调用start()方法,线程正式启动,线 程处于可运行状态,但并不代表线程已经开始运行。
3、run:表示线程处于运行状态。线程处于runnable状态时,抢到CPU运行资源后就会进入运行状态
4、block:表示线程阻塞,等待获取锁,如碰到synchronized、lock等关键字等占用临界区的情况,一旦获取到锁就进行RUNNABLE状态。
5、waiting:表示线程处于无限制等待状态,等待一个特殊的事件来重新唤醒,如通过wait()方法进行等待的线程等待一个notify()或者notifyAll()方法,通过join()方法进行等待的线程等待目标线程运行结束而唤醒,一旦通过相关事件唤醒线程,线程就进入了RUNNABLE状态继续运行。
6、sleeping():表示线程进入了一个有时限的等待,如sleep(3000),等待3秒后线程重新进行RUNNABLE状态继续运行。
7、TERMINATED:表示线程执行完毕后,进行终止状态
注意:一旦线程通过start方法启动后就再也不能回到初始NEW状态,线程终止后也不能再回到RUNNABLE状态。
3.线程的安全问题
1、线程安全问题产生的原因:
(1):多个线程同时访问一个资源。
(2):操作共享数据的代码有多条。
2、怎么解决线程安全问题
(1):只需要在别的线程在执行共享数据的时候其他线程不参与其中就行了。那么这时我们就能利用到同步
synchronized(this/对象) {
//需要同步的语句。
//这个同步对象相当于一把锁。当0线程在处理时1线程和其他线程是进不去的只有他执行完才行。
}
3、同步的弊端:
会产生死锁问题。
死锁产生的原因:
1、互斥条件:
即某个资源在一段时间内只能由一个进程占有,不能同时被两个或两个以上的进程占有。
2、进程所获得的资源在未使用完毕之前,资源申请者不能强行地从资源占有者手中夺取资源,
而只能由该资源的占有者进程自行释放。
3、进程至少已经占有一个资源,但又申请新的资源;由于该资源已被另外进程占有,此时该进程阻塞;但是,它在等待新资源之时,仍继续占用已占有的资源。
4、存在一个等待序列{P1,P2,…,Pn},其中P1等待P2所占有的某一资源,P2等待P3所占有的某一源,…,而Pn等待P1所占有的的某一资源,形成一个进程循环等待环。
总的来说死锁前提要有锁的嵌套,就是两个线程抢资源时,A线程抢到资源a时加锁,同时又要访问b资源,而b资源正被B线程锁着,而B线程又要访问a资源,a资源又被A线程锁着,两者就在这互相索要资源,然而谁都不能得到资源,这就是死锁。可以看以下代码:
public class SynDemo03 {
public static void main(String[] args) {
//创建两个object对象用来测试
Object g =new Object();
Object m = new Object();
Test t1 =new Test(g,m);
Test2 t2 = new Test2(g,m);
//创建两个线程
Thread proxy1 = new Thread(t1);
Thread proxy2 = new Thread(t2);
//线程开启
proxy1.start();
proxy2.start();
}
}
/**
*实现Runnable接口,实现你多线程
*/
class Test implements Runnable{
//商品
Object goods;
//钱
Object money;
//构造器进行初始化。
public Test(Object goods, Object money) {
super();
this.goods = goods;
this.money = money;
}
@Override
public void run() {
//循环调用方法
while(true){
test();
}
}
public void test(){
//此处锁的是商品
synchronized(goods){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//此处锁的是钱
synchronized(money){
}
//此处是嵌套锁
}
System.out.println("一手给钱");
}
}
class Test2 implements Runnable{
//商品
Object goods;
//钱
Object money;
//构造器初始化
public Test2(Object goods, Object money) {
super();
this.goods = goods;
this.money = money;
}
@Override
public void run() {
//循环调用Test方法
while(true){
test();
}
}
public void test(){
//Test1类中此处锁的是goods
//此类中锁的是money
synchronized(money){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//而此处锁的则是商品。
synchronized(goods){
}
}
System.out.println("一手给货");
}
}
这里产生死锁的原因就是:proxy1线程拿到商品后去拿钱,而钱被proxy2线程占用并且加锁。而同时proxy2拿到钱后向proxy1要商品,而此时商品被proxy1锁着,proxy1要不到钱就阻塞在这了,同时一直锁着商品,同理proxy2要不到商品也一直锁着钱。这样就产生了死锁。