Java多线程理解
1.程序、进程、线程
1.1什么是程序,进程,线程?
程序:是为了完成待定任务、用某种语言编写的一组指令的集合。即指 一段静态代码 ,静态对象
进程:是程序的一次执行过程,或是 正在运行的一个程序 。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
->如:运行中的QQ,运行中的MP3播放器…
-> main() --main主线程、gc垃圾回收、异常处理线程
** 线程: 进程可进一步细化为线程,是一个程序内部的一条执行路径。
->若一个进程同一时间并执行多个线程,就是支持多线程的**
2.多线程的优点
1.提高应用程序的响应。对图像化界面更有意义,可增强用户体验。
2.提高计算机系统CPU的利用率。
3.改善程序结构。即长又复杂的进程分为多个线程,独立运行,利于理解和修改。
3.判断是否为多线程
public class Sample{
public void method1(String str){
System.out.println(str);
}
public void method2(String str){
method1(str);
}
public static void main(String[] args){
Sample s = new Sample();
s.method2("hello");
}
}
4.线程的创建和使用
利用继承的方式创建线程 第一种:
class MyThread extends Thread{
// 重写run()
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
if( i % 2 == 0){
System.out.println(i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
// 创建Thread类的子类的创建的对象
Thread t1 = new MyThread();
t1.start();
System.out.println("hello");
}
}
利用实现Runable接口方式创建线程 第二种:
class MThread implements Runnable{
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
if( i % 2==0){
System.out.println(i);
}
}
}
}
public class ThreadTest2 {
public static void main(String[] args) {
MThread mThread = new MThread();
Thread t1 = new Thread(mThread);
t1.start();
}
}
比较创建线程的两种方式:
开发中,优先选择实现Runable接口的方式
原因1:实现的方式没有类的单继承性的局限性
2:实现的方式更适合用来处理多个线程有共享数据的情况
联系:Thread也是实现自Runable,两种方式都需要重写run()方法,将线程要执行的逻辑声明在run中
利用实现callable接口方式创建线程 第三种:
与使用runnable方式相比,callable功能更强大些:
runnable重写的run方法不如callaalbe的call方法强大,call方法可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助FutureTask类,比如获取返回结果
class NumThread implements Callable{
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <=100 ; i++) {
if(i % 2 ==0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadTest2{
public static void main(String[] args) {
NumThread numThread = new NumThread();
//将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numThread);
new Thread(futureTask).start();
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
try {
Object sum = futureTask.get();
System.out.println("总共为:"+sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
利用线程池方式创建线程 第四种:
背景:
经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:
提前创建好多个线程,放入线程池之,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。类似生活中的公共交通工具。
好处:
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理
创建线程池有哪几种方式
①. newFixedThreadPool(int nThreads)
创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程规模将不再变化,当线程发生未预期的错误而结束时,线程池会补充一个新的线程。
②. newCachedThreadPool()
创建一个可缓存的线程池,如果线程池的规模超过了处理需求,将自动回收空闲线程,而当需求增加时,则可以自动添加新线程,线程池的规模不存在任何限制。
③. newSingleThreadExecutor()
这是一个单线程的Executor,它创建单个工作线程来执行任务,如果这个线程异常结束,会创建一个新的来替代它;它的特点是能确保依照任务在队列中的顺序来串行执行。
④. newScheduledThreadPool(int corePoolSize)
创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。
class NumberThread implements Runnable{
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
if(i % 2 ==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
if(i % 2 !=0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
public class ThreadPool{
public static void main(String[] args) {
// 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
// 执行指定的线程的操作。需要提供实现Runnable接口或者Callable接口实现类的对象
// service.execute();//适合适用于Runnable
service.execute(new NumberThread());
service.execute(new NumberThread1());
// service.submit();//适合适用于Callable
service.shutdown();
}
}
5.线程的相关API
获取线程名 :
Thread.currentThread().getName()
1.start():1.启动当前线程2.调用线程中的run方法
2.run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
3.currentThread():静态方法,返回执行当前代码的线程
4.getName():获取当前线程的名字
5.setName():设置当前线程的名字
6.yield():主动释放当前线程的执行权
7.join():在线程中插入执行另一个线程,该线程被阻塞,直到插入执行的线程完全执行完毕以后,该线程才继续执行下去
8.stop():过时方法。当执行此方法时,强制结束当前线程。
9.sleep(long millitime):线程休眠一段时间
10.isAlive():判断当前线程是否存活
6.线程相关例子
车站卖票:
/**
*
* 创建三个窗口卖票,总票数为100张,使用继承自Thread方式
* 用静态变量保证三个线程的数据独一份
*
* 存在线程的安全问题,有待解决
*
* */
class window extends Thread{
// private int ticket = 100;
private static int ticket = 100;
@Override
public void run() {
while (true){
if(ticket>0){
System.out.println(getName() + ":买票,票号为:"+ticket);
}else {
break;
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
window ti = new window();
window t2 = new window();
window t3 = new window();
ti.setName("窗口1");
ti.setName("窗口2");
ti.setName("窗口3");
ti.start();
t2.start();
t3.start();
}
}
7.线程安全问题:
什么是线程安全问题呢?
线程安全问题是指,多个线程对同一个共享数据进行操作时,线程没来得及更新共享数据,从而导致另外线程没得到最新的数据,从而产生线程安全问题。
方法一:同步代码块
Synchronized(同步锁)
Synchronized(同步监视器){
//需要被同步的代码
}
说明:
1.操作共享数据的代码,即为需要被同步的代码
2.共享数据:多个线程操作的变量。比如:ticket就是共享数据。
3.同步监视器,俗称:锁。任何一个类的对象都可以充当锁。
要求:多个线程必须要共用一把锁。
class window1 implements Runnable{
private int ticket = 100;
Object obj = new Object();
@Override
public void run() {
while(true){
synchronized (obj){
//synchronized (windows1.class){ 类也是对象
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"当前售出第"+ticket+"张票");
ticket--;
}else{
break;
}
}
}
}
}
public class ThreadTest3 {
public static void main(String[] args) {
window1 w = new window1();
//虽然有三个线程,但是只有一个窗口类实现的Runnable方法,由于三个线程共用一个window对象,所以自动共用100张票
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();
}
}
方法二:同步方法
class window3 implements Runnable{
private int ticket = 100;
@Override
public void run() {
while(true){
show();
}
}
private synchronized void show(){
if(ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":买票,票号为:"+ticket);
ticket--;
}
}
}
public class windowTest3 {
public static void main(String[] args) {
window3 w = new window3();
//虽然有三个线程,但是只有一个窗口类实现的Runnable方法,由于三个线程共用一个window对象,所以自动共用100张票
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();
}
}
方式三:lock锁方法
class windows implements Runnable{
private int ticket = 100;
// 1.实例化ReetrantLock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
//2.调用lock()
lock.lock();
if (ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":售票,票号"+ticket);
ticket--;
}else {
break;
}
}finally {
// 调用lock解锁
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
windows w = new windows();
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();
}
}
** 面试题 :synchronize 与 Lock的异同?**
== 相同:二者都是解决线程安全问题==
== 不同:synchronize 机制自动在执行完相应的同步代码以后,自动的释放同步监视器==
Lock需要手动的启用同步( Lock() ),同时结束同步也需要手动的实现(unLock())
synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
8.什么是死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。产生死锁的原因,主要包括:
系统资源不足;
程序执行的顺序有问题;
资源分配不当等。
8.1死锁的代码
public class DeadLockDemo implements Runnable{
public static int flag = 1;
//static 变量是 类对象共享的
static Object o1 = new Object();
static Object o2 = new Object();
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":此时 flag = " + flag);
if(flag == 1){
synchronized (o1){
try {
System.out.println("我是" + Thread.currentThread().getName() + "锁住 o1");
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "醒来->准备获取 o2");
}catch (Exception e){
e.printStackTrace();
}
synchronized (o2){
System.out.println(Thread.currentThread().getName() + "拿到 o2");//第24行
}
}
}
if(flag == 0){
synchronized (o2){
try {
System.out.println("我是" + Thread.currentThread().getName() + "锁住 o2");
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "醒来->准备获取 o2");
}catch (Exception e){
e.printStackTrace();
}
synchronized (o1){
System.out.println(Thread.currentThread().getName() + "拿到 o1");//第38行
}
}
}
}
public static void main(String args[]){
DeadLockDemo t1 = new DeadLockDemo();
DeadLockDemo t2 = new DeadLockDemo();
t1.flag = 1;
new Thread(t1).start();
//让main线程休眠1秒钟,保证t2开启锁住o2.进入死锁
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.flag = 0;
new Thread(t2).start();
}
}
代码中, t1创建,t1先拿到o1的锁,开始休眠3秒。然后 t2线程创建,t2拿到o2的锁,开始休眠3秒。然后 t1先醒来,准备拿o2的锁,发现o2已经加锁,只能等待o2的锁释放。 t2后醒来,准备拿o1的锁,发现o1已经加锁,只能等待o1的锁释放。 t1,t2形成死锁。
8.2解决办法
死锁一旦发生,我们就无法解决了。所以我们只能避免死锁的发生。 既然死锁需要满足四种条件,那我们就从条件下手,只要打破任意规则即可。
1.(互斥)尽量少用互斥锁,能加读锁,不加写锁。当然这条无法避免。
2. (请求和保持)采用资源静态分配策略(进程资源静态分配方式是指一个进程在建立时就分 配了它需要的全部资源).我们尽量不让线程同时去请求多个锁,或者在拥有一个锁又请求不到下个锁时,不保持等待,先释放资源等待一段时间在重新请求。
3. (不剥夺)允许进程剥夺使用其他进程占有的资源。优先级。
4. (循环等待)尽量调整获得锁的顺序,不发生嵌套资源请求。加入超时。