Java中的多线程
Java中的多线程
多线程的创建
方式一:继承于Thread类
- 创建一个继承于Thread类的子类
- 重写Thread类的run()
- 创建Thread类的子类的对象
- 通过此对象调用start()
public class ThreadTest {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
//如果执行t1.run()相当于直接调用run方法,不再是多线程
}
}
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i%2==0){
System.out.println(i);
}
}
}
}
方式二:实现Runnable接口
- 创建一个实现了Runnable接口的类
- 实现类去实现Runnable中的抽象方法:run()
- 创建实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 通过Thread类对象调用start()
class MThread implements Runnable{
@Override
public void run() {
//TODO
}
}
public class ThreadTest {
public static void main(String[] args) {
MThread mThread = new MThread();
Thread t1 = new Thread(mThread);
t1.start();
}
}
两种方式的比较
开发中优先选择实现Runnable接口的方式
- 避免了单继承的局限性
- 多个线程可以共享同一个接口实现类的对象,适合多个线程来处理同一份资源
方式三 实现Callable接口
- 创建一个实现Callable的实现类
- 实现call方法,将此线程需要执行的操作声明在call()中
- 创建Callable接口实现类的对象
- 将此对象作为参数传递到FutureTask构造器中,创建FutureTask对象
- 将FutureTask对象作为参数传递到Thread类的构造器中,创建Thread对象并用start方法启动
- 【可选】获取Callable中的call方法的返回值
class NumThread implements Callable<Integer> {
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
if(i%2==0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
NumThread numThread = new NumThread();
//创建FutureTask对象
FutureTask<Integer> futureTask = new FutureTask<Integer>(numThread);
//启动线程
new Thread(futureTask).start();
//只当需要获取call的返回值时调用
try {
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
Object sum = futureTask.get();
System.out.println("总和为:"sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
- 相比run方法,可以有返回值
- 方法可以抛出异常
- 支持泛型的返回值
- 需要借助FutureTask类,比如获取返回结果
方式四:使用线程池
提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁实现重复利用。
- 提供指定线程数量的线程池
- 执行指定的线程操作,需要提供实现Runnable接口或Callable接口实现类的对象
- 关闭连接池
class NumberThread implements Runnable{
@Override
public void run() {
//TODO
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
//TODO
}
}
public class ThreadPool {
public static void main(String[] args) {
//ExecutorService为接口
//多态,实际类型为ThreadPoolExecutor
ExecutorService executorService = Executors.newFixedThreadPool(10);
//ThreadPoolExecutor是ExecutorService的实现类
//可以通过该实现类的对象来进行线程管理
ThreadPoolExecutor service = (ThreadPoolExecutor)executorService;
service.setCorePoolSize(15);
executorService.execute(new NumberThread()); //适合使用于Runnable
executorService.execute(new NumberThread1()); //适合使用于Runnable
//executorService.submit(Callable callable); //适合使用于Callable
//关闭连接池
executorService.shutdown();
}
}
- 提高响应速度
- 降低资源消耗
- 便于线程管理
- corePoolSize :核心池的大小
- maximumPoolSize :最大线程数
- keepAliveTime :线程没有任务时最多保持多长时间后会终止
Thread类中常用方法
-
start():启动当前线程,调用当前线程的run()
-
run()
-
currentThread():静态方法,返回执行当前代码的线程
Thread.currentThread().setName("主线程"); //在主线程中执行设置主线程的名字 -
getName():获取当前线程的名字
-
setName():设置当前线程的名字
设置线程名字还可以通过Thread类的构造器进行赋值
-
yield():暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
-
join():在线程a中调用线程b的join(),此时线程a进入阻塞状态,直到线程b完全执行完以后结束阻塞状态。
try { t1.join(); } catch (InterruptedException e) { e.printStackTrace(); } -
sleep(long millitime):令当前活动线程在指定时间段放弃对CPU控制,时间到后重新排队
-
isAlive():判断线程是否还存活
以下方法必须使用在同步代码块或同步方法中,他们都定义在Object类中:
- wait():阻塞线程,并释放同步监视器
- notify():唤醒一个线程,如果有多个线程wait,就唤醒优先级高的线程
- notifyAll():唤醒所有wait的线程
这三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则出现异常
sleep()和wait()的异同
- 相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态
- 不同点:
- 两个方法声明的位置不同,Thread类中声明sleep(),Object类中声明wait()
- 两个方法的要求不同:sleep()可以在任何需要的场景下调用,wait()必须在同步代码块或同步方法中调用
- sleep方法不会释放锁,wait方法会
线程的优先级
静态变量:
- MAX_PRIORITY: 10
- MIN_PRIORITY: 1
- NORM_PRIORITY: 5 默认优先级
设置优先级:setPriority( int )
高优先级的线程会抢占低优先级线程cpu的执行权
高优先级的线程有更高概率被执行,并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行
同步机制
方式一:同步代码块
相当于操作系统中的临界区,每次只能有一个线程进入临界区
synchronized(同步监视器){
//需要被同步的代码
}
操作共享数据的代码,即为需要被同步的代码
同步监视器(锁):任何一个类的对象都可以充当锁,但多个线程必须要共用同一把锁。
class MThread implements Runnable{
private int ticket = 100;
Object obj = new Object(); //继承方法中必须加static确保唯一,这里不用
@Override
public void run() {
while(true){
synchronized (obj){//任意对象,也可以用this,继承方法中不可以用this
if(ticket > 0){
System.out.println(Thread.currentThread().getName() + "售票号:" + ticket);
ticket--;
}else
break;
}
}
}
}
方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们可以将该方法声明为同步的
- 实现接口方法中,此时的同步监视器为this
private synchronized void show(){
if(ticket > 0){
System.out.println(Thread.currentThread().getName() + "售票号:" + ticket);
ticket--;
}
}
- 继承Thread类方式中,此时同步监视器为当前类即 xxx.class
private static synchronized void show(){ //必须为静态方法
if(ticket > 0){
System.out.println(Thread.currentThread().getName() + "售票号:" + ticket);
ticket--;
}
}
方式三:Lock锁
class MThread implements Runnable{
private int ticket = 100;
//实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true){
try {
//调用lock
lock.lock();
if(ticket > 0){
System.out.println(Thread.currentThread().getName() + "售票号:" + ticket);
ticket--;
}else
break;
}finally {
//调用解锁方法
lock.unlock();
}
}
}
}
synchronized和lock的区别
synchronized机制在执行完相应的同步代码后,自动的释放同步监视器
lock需要手动的启动同步(lock()),结束同步也需要手动的实现(unlock())
解决懒汉式单例模式中的线程不安全问题
方式一:效率较差
class Bank{
private Bank(){}
private static Bank bank = null;
public static synchronized Bank getInstance(){
if(bank == null)
bank = new Bank();
return bank;
}
}
方式二:效率更高,避免了后来的线程进入临界区等待
class Bank{
private Bank(){}
private static Bank bank = null;
public static Bank getInstance(){
if(bank == null){
synchronized (Bank.class){
if(bank == null)
bank = new Bank();
}
}
return bank;
}
}

浙公网安备 33010602011771号