1.什么是线程?
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。
2.进程和线程的区别
1. 进程是操作系统资源分配的基本单位,而线程是CPU的基本调度单位。
2. 一个程序运行后至少有一个进程。
3. 一个进程可以包含多个线程,但是至少需要有一个线程,否则这个进程是没有意义。
4. 进程间不能共享数据段地址,但是同进程的线程之间可以。
2.1线程的组成
任何一个线程都具有基本的组成部分CPU时间片: 操作系统(OS)会为每个线程分配执行时间。 运行数据:
堆空间: 存储线程需要的对象,多个线程可以共享堆中的数据。 栈空间: 存储线程需使用的局部变量,每个线程都拥有独立的栈。
线程的逻辑代码.
2.2线程的特点
1. 线程抢占式执行,效率高,可防止单一线程长时间独占CPU。
2. 在单核CPU中,宏观上同时执行,微观上顺序执行。
3.创建线程的三种方式
1. 继承Thread类,重写run方法
2. 实现Runnable接口
3. 实现Callable接口
继承Thread类的代码:
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("线程======================"+i);
}
}
}
测试:
public class Test01 {
public static void main(String[] args) {
MyThread m1 = new MyThread();
m1.start();
for (int i = 0; i < 20; i++) {
System.out.println("主线程============="+i);
}
}
}
继承Runnable接口:四个窗口卖100张票
public class MyThread implements Runnable{
private Integer ticket = 100;
Lock l = new ReentrantLock();
@Override
public void run() {
while (true){
try {
if (ticket>0){
ticket--;
System.out.println(Thread.currentThread().getName()+"卖了一张还剩:"+ticket+"张");
}else {
break;
}
}
}
}
}
测试:
public class Test01 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread t1 = new Thread(myThread,"窗口A");
Thread t2 = new Thread(myThread,"窗口B");
Thread t3 = new Thread(myThread,"窗口C");
Thread t4 = new Thread(myThread,"窗口D");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
获取线程的名字和设置线程的名字:
MyThread m1 = new MyThread();
m1.setName("一号窗口");
String name = m1.getName();
System.out.println(name);
4.线程中的方法
4.1休眠
public static void sleep(long millis) 当前线程主动休眠millis毫秒。
public class TakeSleep {
public static void main(String[] args) {
T t = new T();
t.start();
for (int i=0;i<20;i++){
System.out.println("main==========="+i);
}
}
}
class T extends Thread{
@Override
public void run() {
for (int i=0;i<20;i++){
try {
Thread.sleep(100);//是当前的线程进入一段休眠状态
System.out.println("这是第"+i+"次");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
}
}
}
}
4.2放弃
public static void yield() 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片
代码:
public class T extends Thread{
@Override
public void run() {
for (int i=0;i<20;i++){
Thread.yield();//可能导致t1和t2的交替的频率变得高了。当前的线程主动放弃时间片,回到就绪状态,再和其他的竞争下一次的时间片
System.out.println(Thread.currentThread().getName()+"第"+i+"次");
}
}
}
测试:
public class TestYield {
public static void main(String[] args) {
T t1 = new T();
t1.setName("张三");
T t2 = new T();
t2.setName("李四");
t1.start();
t2.start();
}
}
public class TestYield {
public static void main(String[] args) {
T t1 = new T();
t1.setName("张三");
T t2 = new T();
t2.setName("李四");
t1.start();
t2.start();
}
}
4.3加入
public final void join() 允许其他线程加入到当前线程中
代码:
public class ThreadJoin extends Thread{
@Override
public void run() {
for (int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+"这是第"+i+"次");
}
}
}
测试:
public class Test01 {
public static void main(String[] args) throws Exception{
ThreadJoin j = new ThreadJoin();
j.setName("A");
j.start();
j.join();
//把线程ThreadJoin加入当前的main线程中,直到ThreadJoin线程执行完毕后,main才会执行
for (int i=0;i<20;i++){
System.out.println("main==================="+i);
}
}
}
4.4守护线程
线程对象.setDaemon(true);设置为守护线程。 线程有两类:用户线程(前台线程)和守护线程(后台线程)如果程序中所有前台线程都执行完毕了,后台线程也会自动结束。
代码:
public class ThreadDaemon extends Thread{
@Override
public void run() {
for (int i=0;i<50;i++){
System.out.println(Thread.currentThread().getName()+"第"+i+"次");
}
}
}
测试:
public class Test01 {
public static void main(String[] args) {
ThreadDaemon t = new ThreadDaemon();
t.setName("A");
t.setDaemon(true);//设置ThreadDaemon为守护线程,当前台线程都执行完毕后,守护线程也会自动结束
t.start();
for (int i=0;i<20;i++){
System.out.println("main==========="+i);
}
}
}
4.5线程安全问题
代码:
public class ThreadSaft {
private static String[] array = new String[5];
private static int index=0;
public static void main(String[] args) throws Exception{
Runnable hello = new Runnable() {
@Override
public void run() {
synchronized (array) {
if (array[index] == null) {
array[index] = "hello";
index++;
}
}
}
};
Runnable world = new Runnable() {
@Override
public void run() {
synchronized (array) {
if (array[index] == null) {
array[index] = "world";
index++;
}
}
}
};
Thread t1 = new Thread(hello);
Thread t2 = new Thread(world);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(Arrays.toString(array));
}
}
4.6线程安全问题的解决办法
同步方法:
synchronized 返回值类型 方法名称(形参列表0){ // 对当前对象(this)加锁
// 代码(原子操作)
}
synchronized (Lock.b){
System.out.println(Thread.currentThread().getName()+"获取到一根筷子");
System.out.println("可以吃饭了");
}
5.线程死锁
当A线程拥有锁资源a时,这时A线程需要锁资源b, 而B线程拥有锁资源b,这时B线程需要锁资源a, 这样会导致A等待B线程释放资源b, B线程等待A线程释放锁资源a。 从而二个处于永久等待。从而操作死锁。
死锁的原因:锁与锁之间有嵌套导致。
解决死锁的办法:
1. 尽量减少锁得嵌套。
2. 可以使用一些安全类。
3. 可以使用Lock中得枷锁,设置枷锁时间。
public class Lock {
public static Object a = new Object();
public static Object b = new Object();
}
public class Boy extends Thread{
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (Lock.a){
System.out.println(Thread.currentThread().getName()+"获取到一根筷子");
synchronized (Lock.b){
System.out.println(Thread.currentThread().getName()+"获取到一根筷子");
System.out.println("可以吃饭了");
}
}
}
}
public class Girl extends Thread{
@Override
public void run() {
synchronized (Lock.b){
System.out.println(Thread.currentThread().getName()+"获取到一根筷子");
synchronized (Lock.a){
System.out.println(Thread.currentThread().getName()+"获取到一根筷子");
System.out.println("可以吃饭了");
}
}
}
}
测试:
public class Test {
public static void main(String[] args) {
Boy b = new Boy();
Girl g = new Girl();
b.setName("李先生");
g.setName("摩托哥");
b.start();
g.start();
}
}
6.线程通信
都不是Thread类中,而是Object类中
等待:释放锁,进入等待队列
public final void wait()
public final void wait(long timeout)
通知:唤醒等待队列中的线程,进入到就绪队列,参与CPU的竞争
public final void notify();唤醒一个
public final void notifyAll()唤醒所有
取钱存钱:
public class BankCard01 {
private double balance;
private boolean flag = false;
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public synchronized void save(double money){
if (flag==true){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//存钱
this.balance = this.balance+money;
System.out.println(Thread.currentThread().getName()+"存入了"+money+"现在卡里的金额为:"+this.balance);
flag=true;
this.notify();
}
public synchronized void task(double money){
if (flag==false){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//取钱
this.balance = this.balance-money;
System.out.println(Thread.currentThread().getName()+"取出了"+money+"现在卡里的金额为:"+this.balance);
flag=false;
this.notify();
}
}
存钱:
public class BoyTask extends Thread{
private BankCard01 bankCard01;
public BoyTask(BankCard01 bankCard01){
this.bankCard01 = bankCard01;
}
@Override
public void run() {
for (int i=0;i<10;i++){
bankCard01.save(1000);
}
}
}
取钱:
public class GirlTask extends Thread{
private BankCard01 bankCard01;
public GirlTask(BankCard01 bankCard01){
this.bankCard01 = bankCard01;
}
@Override
public void run() {
for (int i=0;i<10;i++){
bankCard01.task(1000);
}
}
}
测试:
public class Test02 {
public static void main(String[] args) {
BankCard01 bankCard01 = new BankCard01();
BoyTask b = new BoyTask(bankCard01);
GirlTask g = new GirlTask(bankCard01);
b.setName("king");
g.setName("马闹鸡");
b.start();
g.start();
}
}
7.线程池
该池子中预先存储若干个线程对象,整个池子就是线程池
7.1线程池的作用
线程容器,可设定线程分配的数量上限
将预先创建的线程对象存入池中,并重用线程池中的线程对象
避免频繁的创建和销毁
7.2创建线程池的方式
//Executor:线程池的根类,里面有一个方法。executor执行线程任务的方法Runnable类型的任务
//ExecutorService:线程池的子接口
//shutdown():关闭线程池,需要等待线程池中任务执行完毕后才会关闭
//shutdownNow():立即关闭线程池
//isTerminated():判断线程池是否终止了
//submit():提交任务给线程池中线程对象、Runnable和Callable类型的任务
public class Test {
public static void main(String[] args) {
//创建单一线程池:适应场景:队列要求线程有序执行
//ExecutorService executorService = Executors.newSingleThreadExecutor();
//创建固定长度的线程池对象
//ExecutorService executorService = Executors.newFixedThreadPool(3);
//创建可变长度的线程池
//ExecutorService executorService = Executors.newCachedThreadPool();
//创建延迟线程池对象
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
for (int i=0;i<5;i++){
executorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~~~~~~~~~~~~");
}
},3,TimeUnit.SECONDS);
}
executorService.shutdown();
}
}
8.使用最原始的方式创建线程池
//int corePoolSize, 核心线程数
//int maximumPoolSize, 最大线程数
//long keepAliveTime, 空闲时间
//TimeUnit unit, 时间单位
//BlockingQueue<Runnable> workQueue 堵塞队列
public class Test02 {
public static void main(String[] args) {
BlockingQueue blockingQueue = new LinkedBlockingDeque(3);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,5,10, TimeUnit.SECONDS,blockingQueue);
for (int i=0;i<5;i++){
threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~~~~~~~~~~~~~~~~~");
}
});
}
threadPoolExecutor.shutdown();
}
}
9.创建线程的第三种方式
实现Callable接口,它和实现Runnable接口差不多,只是该接口种的方法有返回值和异常抛出。
public class My1 implements Callable<Integer> {
private Integer sum=0;
@Override
public Integer call() throws Exception {
for (int i=1;i<=50;i++){
sum+=i;
}
return sum;
}
}
public class My2 implements Callable<Integer> {
private Integer sum=0;
@Override
public Integer call() throws Exception {
for (int i=51;i<=100;i++){
sum+=i;
}
return sum;
}
}
测试:
public class Test {
public static void main(String[] args) throws Exception{
My1 m1 = new My1();
My2 m2 = new My2();
//自建创建线程对象并提交Callable类型的任务是比较麻烦的,需要封装到一个 FutureTask类中,建议使用线程池来提交任务。
//FutureTask futureTask = new FutureTask(m1);
//Thread t1 = new Thread(futureTask);
//t1.start();
//System.out.println(futureTask.get());
ExecutorService executorService = Executors.newFixedThreadPool(5);
Future<Integer> future1 = executorService.submit(m1);
Future<Integer> future2 = executorService.submit(m2);
Integer integer1 = future1.get();
Integer integer2 = future2.get();
System.out.println(integer1+integer2);
}
}
10.手动锁
Lock它是手动锁的父接口,它下面有很多实现类。
lock()方法。
unlock()释放锁资源,放在finally中

本文详细解析了线程的概念,进程与线程的区别,线程的组成、特点和创建方式,包括继承Thread、Runnable接口和Callable接口。此外,讲解了线程安全、死锁、通信、线程池及其应用,以及手动锁和Lock接口的使用。
855

被折叠的 条评论
为什么被折叠?



