Java多线程:线程安全

线程安全

导致安全问题出现的原因

是否是多线程环境

是否有共享数据

是否有多条语句操作共享数据

同步(synchronized)

在多线程中,可能有多个线程试图访问一个有限的资源,必须预防这种情况的发生。所以引入了同步机制:在线程使用一个资源时为其加锁,这样其他的线程便不能访问那个资源了,直到解锁后才可以访问。

格式

synchronized(//对象){
    //需要同步的代码;
}

同步可以解决安全问题的根本原因就在那个对象上,该对象如同锁的功能。

锁对象指的是谁?

同步代码块的锁对象是谁:任意对象

同步方法的锁对象是谁:this

静态方法的锁对象是谁:类的字节码文件对象

我们到底使用谁?

如果锁对象是this,就可以考虑使用同步方法。

否则能使用同步代码块的尽量使用同步代码块。

如何把一个线程不安全的集合类变成一个线程安全的集合类?

用Collections工具类的方法即可:
List<String> list = Collections.synchronizedList(new ArrayList<String>()); 

为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,位于java.util.concurrent.locks包下Lock接口

void lock()//获取锁
void unlock()//释放锁
public class SellTicket implements Runnable {
	// 定义票
	private int tickets = 100;
	// 定义锁对象
	private Lock lock = new ReentrantLock();

	@Override
	public void run() {
		while (tickets>0) {
			try {
				// 加锁
				lock.lock();
				if (tickets > 0) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
				}
			} finally {
				// 释放锁
				lock.unlock();
			}
		}
	}
}

public class SellTicketDemo {
	public static void main(String[] args) {
		// 创建资源对象
		SellTicket st = new SellTicket();

		// 创建三个线程对象
		Thread t1 = new Thread(st, "窗口1");
		Thread t2 = new Thread(st, "窗口2");
		Thread t3 = new Thread(st, "窗口3");

		// 启动线程
		t1.start();
		t2.start();
		t3.start();
	}
}

前提

多个线程使用的是同一个锁对象

好处

同步的出现解决了多线程的安全问题

弊端

当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率,效率低。

如果出现了同步嵌套,就容易产生死锁问题。

死锁

死锁是指两个或多个线程无休止地互相等待对方释放所占据资源的过程,错误的同步往往会引起死锁。

死锁产生前提

同步中套着同步。

为了防止死锁,在进行多线程程序设计时必须遵循如下原则:

在指定的任务真正需要并发时,才采用多线程来进行程序设计。

在对象的同步方法中需要调用其他同步方法时必须小心。

在临界区中的时间应尽可能短,需要长时间运行的任务尽量不要放在临界区中。

线程间通信

其实就是多个线程在操作同一个资源,但是操作的动作不同。

等待唤醒

Object类中提供了三个方法:

void wait()//等待
void notify()//唤醒单个线程
void notifyAll()//唤醒所有线程

为什么这些方法不定义在Thread类中呢?

这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象是任意锁对象。所以,这些方法必须定义在Object类中。

线程组

Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。

默认情况下,所有的线程都属于主线程组。

public final ThreadGroup getThreadGroup()

我们也可以给线程设置分组

Thread(ThreadGroup group, Runnable target, String name) 

线程池

线程池的好处

线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。

线程池线程都是后台线程。

每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。

  • 如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器来保持繁忙。
  • 如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。
  • 超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。

在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池。

组成部分

线程池管理器:用于创建并管理线程

工作线程:线程池中线程

任务接口:每个任务必须实现的接口,以供工作线程调度任务的执行

任务队列:用于存放没有处理的任务,提供一种缓冲机制

注意事项

线程池大小

并发错误

线程泄露

简单线程池的设计

线程池管理器:用于启动,停用,管理线程池

工作线程:线程池中的线程

请求接口:创建请求对象,以供工作线程调度任务的执行

请求队列:用于存放和提取请求

结果队列:用于存储请求执行后返回的结果

线程池工具类

JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法:

public static ExecutorService newCachedThreadpool()//创建一个具备高效缓冲效果的线程池对象
public static ExecutorService newFixedThreadpool(int nThreads)//创建一个可重用的线程池,参数用来指定当前线程池中有多少个线程对象
public static ExecutorService newSingleThreadExecutor()//创建一个包含一个线程对象的线程池对象

这些方法的返回值是 ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程,它提供了如下方法

Future<?> submit(Runnable task)
<T> Future <T> sumbit(Callable<T> task)

如何实现线程的代码呢?

  1. 创建一个线程池对象,控制要创建几个线程对象。
  2. 这种线程池的线程可以执行:可以执行Runnable对象或者Callable对象代表的线程,做一个类实现Runnable接口。
  3. 调用submit(Runnable task)即可
  4. 结束线程池
public class MyRunnable implements Runnable {
	@Override
	public void run() {
		for (int x = 0; x < 100; x++) {
			System.out.println(Thread.currentThread().getName() + ":" + x);
		}
	}
}

public class ExecutorsDemo {
	public static void main(String[] args) {
		// 创建一个线程池对象,控制要创建几个线程对象。
		ExecutorService pool = Executors.newFixedThreadPool(2);

		// 可以执行Runnable对象或者Callable对象代表的线程
		pool.submit(new MyRunnable());
		pool.submit(new MyRunnable());

		//结束线程池
		pool.shutdown();
	}
}

匿名内部类方式使用多线程

new Thread(){代码…}.start();
new Thread(new Runnable(){代码…}).start(); 

定时器

可以让我们在指定的时间做某件事情,还可以重复的做某件事情。

依赖Timer和TimerTask这两个类:

Timer:定时

Timer()
void schedule(TimerTask task,long delay)//安排在指定的时间执行指定的任务
void schedule(TimerTask task,long delay,long period)//安排指定的任务从指定的延迟后开始进行重复的固定延迟执行
void cancel()//终止此计时器,丢弃所有当前已安排的任务

TimerTask:定时任务继承此类,重写run()。

public class TimerDemo {
	public static void main(String[] args) {
		// 创建定时器对象
		Timer t = new Timer();
		// 3秒后执行爆炸任务
		t.schedule(new MyTask(t), 3000);
	}
}

// 做一个任务
class MyTask extends TimerTask {

	private Timer t;
	public MyTask() {
	}
	public MyTask(Timer t) {
		this.t = t;
	}

	@Override
	public void run() {
		System.out.println("beng,爆炸了");
		// 结束任务
		t.cancel();
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值