ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Java-线程

2020-10-25 22:03:10  阅读:95  来源: 互联网

标签:Java Thread void start 线程 new public


1.多线程概述

  • 进程:一个程序运行,程序在内存中分配的那片空间。

  • 线程:进程中一个执行单元执行路径

    进程中至少有一个线程,如果进程中有多个线程,就是多线程的程序。

  • 并行与并发:

    并行:某一时间点,有多个程序同时执行,多核CPU运行
    并发:某一时间段,有多个程序同时执行,并不是真正意义的同时执行。 为多线程。
    
  • 并发真的是同时执行吗?

    不是,而是时间间隔很短,造成同时执行感觉。
    
  • 多线程优势?

    提高了用户体验,提高了程序的运行效率,提高CPU使用率。
    

2.开启线程两种方式

  • 开启线程

    /*
     * 1.继承Thread
     * 2.重写run方法
     * 3.创建子类的对象
     * 4.调用start方法
     * */
    public class ThreadDemo1 {
    	public static void main(String[] args) {
            // 创建子类的对象
    		Demo d1 = new Demo("Jack");
    		Demo d2 = new Demo("Tom");
            // 设置线程名字
    		d1.setName("d1");
    		// 获取d1执行线程名字
    		System.out.println(d1.getName());
    		// 获取当前线程名字
    		System.out.println(Thread.currentThread().getName());
            
            // 调用start方法
    		d1.start();
    		d2.start();
    	}
    }
    // 继承Thread
    class Demo extends Thread{
    	String nickName;
    	public Demo(String nickName) {
    		this.nickName = nickName;
    	}
        // 重写run方法
    	public void run() {
    		for(int i=0;i<30;i++) {
    			System.out.println(nickName + "---" + i);
    		}
    	}
    }
    // 整个运行过程有三个线程运行,主线程开启d1和d2线程
    
  • run方法与start方法区别

    start:开启新的线程,会自动调用run方法在新的线程中执行
    run:没有开启新的线程,只是普通方法
    
  • 开启新线程第二种方式

    声明实现Runnable接口的类,该类然后实现run方法,然后可以分配该类的实例,在创建Thread时做为一个参数来传递并启动,采用这种风格的同一个例子
    
    /*
     * 实现多线程第二种方式:
     * 1.实现Runnable
     * 2.重写run方法
     * 3.创建Runnable子类的对象
     * 4.创建Thread类的对象,把第三步的对象传到构造方法中
     * 5.使用Thread子类对象,调用start方法
     * */
    public class ThreadDemo2 {
    	public static void main(String[] args) {
    		Demo5 d = new Demo5();// 只有Thread或子类线程对象才是线程对象。它只是线程任务度夏宁。
    		Thread th = new Thread(d); // th才是线程对象
    		Thread th2 = new Thread(d);
    		th.start();
    		th2.start();
    	}
    }
    
    class Demo5 implements Runnable{
    	public void run() {
    		for (int i=0;i<20;i++) {
    			System.out.println(Thread.currentThread().getName() + "---" + i);
    		}
    	}
    }
    
  • 两种实现方式,区别是?

    第一种方式有局限性的,因为Java是单继承,如果一个类已经有一个继承,它就不能再继承Thread类,就无法实现多线程。而第二种实现通过接口方式实现更合理,并且第二种方式更加符合面向对象特点:高内聚低耦合,把线程对象和线程任务分离开了。
    

3.线程中方法

1.sleep方法使用

public Static void sleep(long millis)
静态方法 进入阻塞状态,时间结束后进入可执行
  • 示例:
Thread.sleep(3000);

sleep方法让谁阻塞,取决于他在哪个线程中。

2.join方法使用

public final void join()
被谁调用,让哪个线程先执行,执行完毕后,再执行所在线程
  • 示例

public class JoinDemo1 {
	public static void main(String[] args) {
		Sum s = new Sum();
		s.start();
		// join:用谁调用,就让那个线程先执行,执行完毕后,再执行他所在线程(将他所在线程阻塞)。
		try {
			s.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		System.out.println(Sum.sum);
	}
}

class Sum extends Thread{
	static int sum = 0;
	public void run() {
		for(int i=0;i<=1000;i++) {
			sum += i;
		}
	}
}

3.yield方法

public static void yield()
让其他线程先执行,不一定生效,因为让谁执行是CPU决定的

4.stop方法

停止一个线程

5.interrupt 方法

打断线程的阻塞状态,进入可执行状态,会抛出异常
  • 示例:打断子线程阻塞
public class interruptDemo {
	public static void main(String[] args) {
		Demo3 d = new Demo3();
		d.start();
		// 将d执行线程阻塞状态打断。
		d.interrupt();
		System.out.println("over");
	}
}


class Demo3 extends Thread{
	public void run() {
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		for (int i=0;i<10;i++) {
			System.out.println(i);
		}
	}
}
  • 打断主线程阻塞
public class InterruptDemo2 {
	public static void main(String[] args) {
		// 主线程传给Demo4
		Demo4 d = new Demo4(Thread.currentThread());
		d.start();
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}	
		System.out.println("over");
	}
}

class Demo4 extends Thread{
	Thread th;
	public Demo4(Thread th) {
		this.th = th;
	}
	// 用于打断主线程
	public void run() {
		th.interrupt();
	}
}
  • 练习:创建两个线程,一个线程负责打印大写字母表,一个线程负责打印小写字母表
public class Threadlianxi1 {
	public static void main(String[] args) {
		Thread th1 = new Thread(new Task1());
		Thread th2 = new Thread(new Task2());
		th1.start();
		th2.start();
	}
}


class Task1 implements Runnable{
	public void run() {
		for(char i='a';i<='z';i++) {
			System.out.println(i);
		}
	}
}
class Task2 implements Runnable{
	public void run() {
		for(char i='A';i<='Z';i++) {
			System.out.println(i);
		}
	}
}

4.线程生命的周期

主线程执行时候在栈空间,开辟空间给子线程,而他们的之间栈是独立的,但他们堆空间数据是共享的

5.线程安全的问题

  • 买票示例:
public class SellTicketsDemo {
	public static void main(String[] args) {
		Tickets t = new Tickets();
		Thread th1 = new Thread(t);
		Thread th2 = new Thread(t);
		Thread th3 = new Thread(t);
		th1.start();
		th2.start();
		th3.start();
	}
}


class Tickets implements Runnable{
	static int tickets = 100;
	public void run() {
		while (true) {
			if (tickets> 0) {
				try {
					Thread.sleep(30);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "====" + "正在出售第" + tickets--  +"张票!");
			} else {
				break;
			}
		}
	}
}

  • 在执行代码时候,会发现多个线程会卖出同一张票。这样会产生线程安全问题。

  • 线程安全产生原因:1.具备多线程。2.操作共享数据。3.操作共享数据的代码有多条。

  • 通过加锁:让每一时刻只能有一个线程操作数据。方式有三种

1.同步代码块

synchronized(锁对象){
	容易产生线程安全问题的代码
}

// 锁对象:可以是任意对象,但是必须保证多个线程使用是同一个对象。
  • 示例:
    • 关键点:锁对象选择
public class SellTicketsDemo {
	public static void main(String[] args) {
		Tickets t = new Tickets();
		Thread th1 = new Thread(t);
		Thread th2 = new Thread(t);
		Thread th3 = new Thread(t);
		th1.start();
		th2.start();
		th3.start();
	}
}


class Tickets implements Runnable{
	static int tickets = 100;
    // 如果o方法run方法里,则无法实现线程安全,原因是执行run方法,实现3个o对象,对于这三个线程来说o对象不是共有的同一个对象。
	Object o = new Object();
	public void run() {
		while (true) {
			synchronized (o) {// o所在的类被new几次
				if (tickets> 0) {
					try {
						Thread.sleep(30);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "====" + "正在出售第" + tickets--  +"张票!");
				} else {
					break;
				}
			}
		}
	}
}
  • 锁对象选取错误示例:
    • Tickets2被new了3次导致,没有锁住。
public class SellTicketsDemo2 {
	public static void main(String[] args) {
		Tickets2 th1 = new Tickets2();
		Tickets2 th2 = new Tickets2();
		Tickets2 th3 = new Tickets2();
		th1.start();
		th2.start();
		th3.start();
	}
}

class Tickets2 extends Thread{
	static int tickets = 100;
	Object o = new Object();
	public void run() {
		while (true) {
			synchronized (o) {
				if (tickets> 0) {
					try {
						Thread.sleep(30);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "====" + "正在出售第" + tickets--  +"张票!");
				} else {
					break;
				}
			}
		}
	}
}
  • 方式1:解决方式通过:构造方法解决:
public class SellTicketsDemo2 {
	public static void main(String[] args) {
		Object o = new Object();
		Tickets2 th1 = new Tickets2(o);
		Tickets2 th2 = new Tickets2(o);
		Tickets2 th3 = new Tickets2(o);
		th1.start();
		th2.start();
		th3.start();
	}
}

class Tickets2 extends Thread{
	static int tickets = 100;
	Object o;
    // 构造方法
	public Tickets2(Object o) {
		this.o=o;
	}

	public void run() {
		while (true) {
			synchronized (o) {
				if (tickets> 0) {
					try {
						Thread.sleep(30);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "====" + "正在出售第" + tickets--  +"张票!");
				} else {
					break;
				}
			}
		}
	}
}
  • 方式2:使用静态方式:
Static Object o = new Object();
  • 方式3:使用字符串的常量
synchronized ("abc")
  • 方式4:使用Class对象
synchronized (SellTicketsDemo2.class)
// 在jvm中,每一个类的Class总共只有一个

2.同步方法

  • 把synchronized放到方法的修饰符中,锁的是整个方法。
public class SellTicketsDemo3 {
	public static void main(String[] args) {
		Tickets3 th1 = new Tickets3();
		new Thread(th1).start();
		new Thread(th1).start();
		new Thread(th1).start();
	}
}

class Tickets3 implements Runnable{
	static int tickets = 100;
	// 默认锁对象:this
	public synchronized void run() {
		while (true) {
				if (tickets> 0) {
					try {
						Thread.sleep(30);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "====" + "正在出售第" + tickets--  +"张票!");
				} else {
					break;
				}
		}
	}
}

上述代码虽然解决了线程安全问题,但是编程了单线程程序,原因synchronized锁的范围太大,第一个进来线程,执行完整个while循环。导致在使用同步方法时候也需要注意这个问题。

  • 但可以通过同步方法解决懒汉式单例模式:
package com.xjk;
// 懒汉式:存在问题,存在线程安全问题
public class Singleton2 {
	private static Singleton2 s;
	// 构造方法私有化,为了不让别人随便new
	private Singleton2() {
	}
	// 通过synchronized 解决线程安全问题
	public synchronized static Singleton2 getInstance() {
		if (s == null) {
			s = new Singleton2();
		} 
		return s;
	}
}
// 当启100个线程,当一个线程执行到s=new Singleton2();此时刚要new Singleton2,cpu切到第二个线程,因为s此时还是等于null,第二个线程也执行new Singleton2() 导出单例模式创建多个对象。此时通过同步方法添加synchronized可以有效解决此问题。
  • 同样StringBuffer是线程安全的内部有synchronized,效率相对StringBuilder低,StringBuilder是线程不安全的。

3.Lock

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SellTicketsDemo4 {
	public static void main(String[] args) {
		Tickets4 th1 = new Tickets4();
		new Thread(th1).start();
		new Thread(th1).start();
		new Thread(th1).start();
	}
}

class Tickets4 implements Runnable{
	static int tickets = 100;
	// 也要保证该锁对象对于多个线程是同一个
	Lock lock = new ReentrantLock();
	public synchronized void run() {
		while (true) {
				lock.lock();// 加锁
				try {
					if (tickets> 0) {
					try {
						Thread.sleep(30);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "====" + "正在出售第" + tickets--  +"张票!");
				} else {
					break;
				}
				} finally {
					lock.unlock();//解锁
				}
		}
	}
}
  • 释放锁的代码放到finally代码块中,否则容易造成程序阻塞。

6.死锁

  • 是指两个或两个以上的线程在执行的过程中,因争夺资源产生一种互相等待现象。

7.线程池用法

  • 线程池可以减少创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

  • 可以根据系统的承受能力,调整线程池中工作线程数目,放置因为消耗过多的内存,而把服务器累死(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

  • 线程池的创建:

    public static ExecutorService newCachedThreadPool()
    	创建一个具有缓存功能的线程池
    public static ExecutorService new FixedThreadPool(int nThreads)
    	创建一个可重用的,具有固定线程数的线程池
    public static ExecutorService newSingleThreadExecutor()
    	创建一个只有单线程的线程池,相当于上个方法的参数是1
    
  • 示例:

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class ThreadPoolDemo {
    	public static void main(String[] args) {
            // 1
    		ExecutorService pool = Executors.newCachedThreadPool();
    		pool.execute(new Runnable() {
    			public void run() {
    				System.out.println("hello world");
    			}
    		});
    		// 关闭线程池
    		pool.shutdown();
            
            // 2
            pool = Executors.newFixedThreadPool(20);
            
    	}
    }
    // 缓存线程池,如果没有任务会等待60s就关闭
    

标签:Java,Thread,void,start,线程,new,public
来源: https://www.cnblogs.com/xujunkai/p/13875512.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有