JAVA多线程基本使用

一、进程和线程

1.进程:正在进行的程序

2.线程:是进程中的单个顺序控制流,是一条执行路径。

线程分为单线程和多线程。所谓单线程就是指程序只能从a->b->c->…,不可以在进程中做着a又执行b。所谓多线程就是指进程有两个或两个以上的执行路径,即可同时执行a与b操作

二、实现多线程

1.继承Thread类
继承Thread类(线程类)的类需要重写run()方法。但是直接调用run()方法不会实现多线程。Thread类中提供了一个start()方法,它可以启动线程,并且让JVM调用run()方法,以此达到多线程的目的。
比如说:

public class MyThread extends Thread{
	public void run(){
		for(int i = 0;i < 100;i ++){
			System.out.println(i);
		}
	}
}

class TestMyThread{
	public static void main(String[] args){
		MyThread m1 = new MyThread();
		MyThread m2 = new MyThread();
		//相当于普通方法,无法实现多线程的目的
		//m1.run();
		//m2.run();
		//实现多线程
		m1.start();
		m2.start();
	}
}

为什么要重写run方法?
因为run方法是封装被线程执行的代码。

run方法与start方法区别?

方法作用/特点
run()直接调用相当于一个普通方法
start()启动线程;使JVM调用run方法,实现多线程目的

2.实现Runnable接口
通过创建一个类并且实现接口Runnable,将这个类作为参数传递给Thread类的构造器。
Thread类提供了如下两个构造器:
Thread(Runnable target);
Thread(Runnable target,String name);
使用Runnable接口实现方法的优点:实现了Runnable接口的类可以去继承一个父类,避免了java单继承的局限性。同时较好的体现了OOP思想。

三、设置、获取线程的名字

1.设置线程名字
a.Thread类

	Thread t1 = new Thread("marry");
	Thread t2 = new Thread();
	t2.setName("Maryy" );
    System.out.println("t1`s name is " + t1.getName() + " t2`s name is " + t2.getName());

b.MyThread类(自定义线程类)

public class MyThread extends Thread{
    public MyThread(){}
    public MyThread(String name){
        super(name);
    }
    public static void main(String[] args){
		MyThread myThread1 = new MyThread("marry-1");
        MyThread myThread2 = new MyThread("marry-2");
        MyThread myThread3 = new MyThread();
        myThread3.setName("Marry-3");
	}
}

两种线程类设置线程名字有些许差别。setName(String name)是在Thread中,因此MyThread可以直接调用。Thread中还存在一个在构造的时候就可以设置线程名称的构造器Thread (String name),MyThread就要使用父类构造器来完成。

2.获取线程名字
getName();
3.获取正在执行线程的对象的引用(说白了就是main方法的线程对象)
Thread.currentThread(),返回的是一个Thread类,如果要知道它的线程名称,则可以使用Thread.currentThread().getName();

四、线程优先级

执行多线程的时候,每一个线程都有一个优先级(Priority)。优先级高的线程可以抢占更大时间比例的CPU时间片,也就是执行这个线程的概率更高。注意:不是线程优先级高就顺序先执行这个高优先级!!!!!!!!
1.设置线程优先级
方法:setPriority(int value)
在设置线程优先级需要注意的是线程的优先级是从1~10,默认优先级是5。不可以超过这个范围,超过这个范围会报错IllegalArgumentException(非法参数错误)。
2.获取线程优先级
方法:getPriority();

五、线程控制

这里介绍三个线程控制的方法

方法名作用
sleep(long ms)控制线程多少ms为一个时间段去抢占cpu时间片
join()等待这个线程运行完,其余的线程再去抢占cpu
setDaemon(boolean on)设置线程为守护线程,当主线程(main线程)运行结束以后若其余线程全是守护线程,则jvm退出。否则jvm不退出,其余线程继续抢占cpu时间片

join()方法要放在start()方法的下一行(或者说是下一个start前) 才发挥作用

六、线程生命周期

线程生命周期流程图

七、线程同步

拿卖票问题来讲,假如有三个线程,且具有相同数据ticketNumber=100,每次卖票以后,ticketNumber-1,如果同时启动三个线程,由于线程的随机性,可能三个线程会同时出售同一张票或者是出现ticketNumber<0的情况。这时候引发了线程的数据安全问题。
1.何时会线程出现数据安全问题
a.多线程 b.有公共数据 c.对数据有一条或多条操作

2.利用同步代码块解决数据安全问题
synchronized(任意对象){
       代码块
}
需要注意的是,synchronized括号里的任意对象不可以直接在括号里new一个对象。理由:如果是new一个对象的话起不到锁住代码块的作用。因为每一个线程在抢占到了CPU之后,遇到了synchronized代码块以后,都会new一个对象来开锁。所以需要在类的属性中先定义一个类作为开锁对象使用。
3.利用同步方法解决数据安全问题
同步方法的定义:
修饰符 synchronized 返回值类型 方法名(参数列表)
同步方法分为普通同步方法和静态(static)同步方法。在同步方法解决数据安全问题上,如果出现了if···else的结构

if(){
	snychronized(对象){
		代码A
	}//利用了同步代码块的方法
}
else{
  	代码B
}

两个代码块都有对公共数据的操作,那么这两个代码块都需要上锁。也就是代码A和代码B需要的锁是同一把锁。如果是普通方法,那么锁对应的是this。如果是静态方法,那么锁对应的是这个类的反射(.class)【字节码文件】。

八、线程安全类

1.Vector
与Vector相对应的线程不安全的类ArrayList执行效率更快。

2.StringBuffer
与StringBuffer相对应的线程不安全的类StringBuilder

3.Hashtable
与Hashtable相对应的线程不安全的类HashMap

在不需要线程安全的时候,尽量使用ArrayList、StringBuilder、HashMap这些类,在需要线程安全的时候,除了StringBuffer以外,也不会使用Vector和Hashtable。通常会用Collections.synchronizedList(List<E> list)方法使list转换为线程安全的集合。

九、Lock锁

同步代码块和同步方法解决线程数据安全问题上,我们用了关键字synchronized,需要加上一个锁。这个锁怎么加上的呢?JAVA提供了一个接口Lock。

获取锁: lock()
释放锁:unlock()

Lock是一个接口,无法直接实例化,需要使用一个已经实现了Lock接口的类ReentrantLock进行实例化。

在使用的时候,通常是这样的一个结构

try{
	lock.lock();
	/*
	//这里都是逻辑代码块,可能会调用线程的一些方法
	if(a > 100){
		  try {
               Thread.sleep(1000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
         System.out.println("a" + i);
	}
	*/
}
finally{
	lock.unlock();
}

使用try-finally块是因为上锁之后的代码可能会出现异常导致最后无法释放锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值