添砖加瓦(java)

多线程

前言:

大家好我是kdy丶

这里写目录标题

    • 添砖加瓦(java)
    • ***多线程***
    • 前言:
  • 一丶程序、进程、线程:
    • 1丶基本概念:
  • 二丶多线程的创建:
    • 1丶方式一:继承Thread类的方式:
    • 2丶Thread类的相关方法:
    • 3丶方式二:实现Runnable接口的方式:
    • 方式三:实现Callable接口
    • 方式四:线程池
  • 三丶线程的同步:
    • 1丶问题的引入:
    • 2丶同步代码块解决:
    • 3丶同步方法解决:
    • 4丶Lock解决:
  • 四丶死锁的问题:
    • 1丶死锁的理解:
  • 五丶线程的通信:
    • 1丶线程的通信
  • 六丶总结:

一丶程序、进程、线程:

1丶基本概念:

1丶 程序(program)
概念:是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码。

2丶 进程(process)
概念:程序的一次执行过程,或是正在运行的一个程序。
说明:进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域

3丶 线程(thread)
概念:进程可进一步细化为线程,是一个程序内部的一条执行路径。
说明:线程作为调度和执行的单位,每个线程拥独立的运行栈和程序计数器(pc),线程切换的开小。

也就是说进程是包括多线程的。

这是一场进程分别为多线程的和单线程的概念图。

我们可以举例子来说明:

就像是我们启动的电脑管家就是一个进程,

而我们电脑管家把这些功能同时开启就是多线程。

二丶多线程的创建:

1丶方式一:继承Thread类的方式:

1丶创建多线程的大致步骤:

1丶创建一个继承于Thread类的子类
2丶重写Thread类的run() 之后将此线程执行的操作声明在run()中
3丶创建Thread类的子类的对象
4丶通过此对象调用start():①启动当前线程 ② 调用当前线程的run()

例:

public class demo01 {public static void main(String[] args) {mythread mt=new mythread();mt.start();Thread.currentThread().setName("主线程");for (int i = 0; i < 100; i++) {if(i%2!=0){System.out.println(Thread.currentThread().getName()+":"+i);}}}}class mythread  extends  Thread{@Overridepublic void run() {Thread.currentThread().setName("线程k1");for (int i = 0; i < 100; i++) {if(i%2==0){System.out.println(Thread.currentThread().getName()+":"+i);}}}
}

运行结果:

我们可以看到,运行出来的结果并不是有规律性的,这就是多线程 ,可能没启动一次都会有不一样的执行结果。

在这里可能大家都会想为什么调用的是start()方法而不是run()方法?其实道理很简单,再主方法中我们无论是创建对象的还是调用方法其实都是在主线程中运行的,是主线程帮我们弄。,因为start():①启动当前线程 ② 调用当前线程的run()的这两个特性功能,开辟的另一条线程。如果我们调用的是run()方法,那么他不会开辟新的线程,run方法还是归为主线程中。

2丶假如我们要如果再启动一个线程,我们要怎么样做?
如果我再次调用 mt.start();
运行结果:

那我们改怎么做?
必须只能重新创建一个Thread子类的对象,调用此对象的start().

2丶Thread类的相关方法:

1丶相关方法:

1丶start():启动当前线程;调用当前线程的run()

2丶 run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中

3丶currentThread():静态方法,返回执行当前代码的线程

4丶 getName():获取当前线程的名字

5丶setName():设置当前线程的名字

6丶yield():释放当前cpu的执行权

我们来看这样一段代码:

public class demo02 {public static void main(String[] args) {yy y=new yy();y.start();Thread.currentThread().setName("主线程");for (int i = 0; i < 20; i++) {System.out.println(Thread.currentThread().getName()+":"+i);}}
}class yy extends Thread{@Overridepublic void run() {Thread.currentThread().setName("线程k1");for (int i = 0; i < 20; i++) {if (i==5){yield();}System.out.println(Thread.currentThread().getName()+":"+i);}}
}

运行结果:

可以看到当我们的线程k1执行到i=5时,就执行了主线程,其实这并不是巧合,而是在线程k1执到i=5的时候,调用了 yield();方法,释放了当前cpu的执行权。主线程才有几率执行。但这不等于让给主线程执行,一旦主线程没来得及执行,那么执行的还是线程k1例的程序。

这就好比,我们排队买奶茶,如果轮到了我,我可以组织一场竞赛,同时抢答问题,谁先出回答出来谁就占据我的位置,执行你的操作,无论是谁都有机会。

7丶 join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。

例:

public class demo02 {public static void main(String[] args) {yy y=new yy();y.start();Thread.currentThread().setName("主线程");for (int i = 0; i < 20; i++) {if (i==10) {try {y.join();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+":"+i);}}
}class yy extends Thread{@Overridepublic void run() {Thread.currentThread().setName("线程k1");for (int i = 0; i < 20; i++) {if (i==5){//             yield();}System.out.println(Thread.currentThread().getName()+":"+i);}}
}

运行结果:

我们在主线程中调用了我们的线程k1,在我们运行到i=10的时候,主线程阻塞,必须要全部等线程k1全部执行完之后才可以进行主线程。

8丶sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态。

9丶 isAlive():判断当前线程是否存活

2丶线程的优先级:
线程的优先级:

MAX_PRIORITY:10
MIN _PRIORITY:1
NORM_PRIORITY:5 (默认优先级)

获取和设置当前线程的优先级:

getPriority():获取线程的优先级
setPriority(int p):设置线程的优先级

说明:高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只当高优先级的线程执行完以后,低优先级的线程才执行。

3丶方式二:实现Runnable接口的方式:

大致步骤:

1丶 创建一个实现了Runnable接口的类
2丶实现类去实现Runnable中的抽象方法:run()
3丶创建实现类的对象
4丶将此对象作为参数传递到Thread类的构造器Thread(Runnable target)中,创建Thread类的对象
5丶通过Thread类的对象调用start()

public class demo03 {public static void main(String[] args) {mythread1 m=new mythread1();Thread thread=new Thread(m);thread.start();for (int i = 0; i < 100; i++) {if (i%2!=0){System.out.println(Thread.currentThread().getName()+":"+i);}}}
}class mythread1 implements Runnable{@Overridepublic void run() {for (int i = 0; i < 100; i++) {if (i%2==0){System.out.println(Thread.currentThread().getName()+":"+i);}}}
}

在这里可能有人会想到,在我们调用run()的时候我们调用的是Thread类中的run(),为什么会跑到mythread1类中调用他的run()方法呢?
我们看下源码:

1)Thread类中的run是这样的方法体:如果target不等于null,那么久调用run()方法;
2)那么这个target究竟是什么呢?

他是定义为Runnable类型的私有变量。

3)正好这个构造器的形参也是Runnable类型的。
也就是说,我们首先要看这个target是否为空如果不为空就调用run()方法,而这个target也就是我们的在上面demo03的主方法里创建对象***mythread1 m=new mythread1();***中的m;

方式三:实现Callable接口

与使用Runnable相比, Callable功能更强大些

相比run()方法,可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助FutureTask类,比如获取返回结果

Callable接口的使用与前两种方式的步骤并不相同:

public class demo10 {public static void main(String[] args) throws ExecutionException, InterruptedException {Numberthread numberthread=new Numberthread();FutureTask futureTask=new FutureTask(numberthread);Thread thread = new Thread(futureTask);thread.start();Object o = futureTask.get();System.out.println(o);}
}class Numberthread implements Callable{@Overridepublic 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;}
}

运行结果:

1丶创建Callable接口实现类的对象。
2丶将Callable实现类的对象作为参数传递给FutrueTask的构造器中,并创建FutrueTask的对象。
3丶将FutrueTask对象作为参数传递给Thread构造器中,调用start(),启动线程。
4丶获取Callable中call方法的返回值。

关于Future接口

1丶可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
2丶FutrueTask是Futrue接口的唯一的实现类
3丶FutureTask 同时实现了Runnable, Future接口。它既可以作为
4丶Runnable被线程执行,又可以作为Future得到Callable的返回值

方式四:线程池

JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors:

ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable

Future submit(Callable task):执行任务,有返回值,一般又来执行Callable
void shutdown() :关闭连接池

Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运
行命令或者定期地执行。

用线程池去创建线程大致分为三部:
1丶提供指定线程数量的线程池
2丶执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
3丶关闭连接池

例:

public class demo11 {public static void main(String[] args) {ExecutorService executorService = Executors.newFixedThreadPool(10);//设置线程属性ThreadPoolExecutor executorService1 = (ThreadPoolExecutor) executorService;executorService1.execute(new sum1());executorService1.execute(new sum2());executorService1.shutdown();}
}
class sum1 implements Runnable{@Overridepublic void run() {for(int i = 0;i <= 100;i++){if(i % 2 == 0){System.out.println(Thread.currentThread().getName() + ": " + i);}}}
}class sum2 implements Runnable{@Overridepublic void run() {for(int i = 0;i <= 100;i++){if(i % 2 != 0){System.out.println(Thread.currentThread().getName() + ": " + i);}}}
}

值得注意的是,因为他是线程池,创建的队形的时候会将线程的个数作为参数传给ExecutorService类中,所以创建的线程不能超过参数的个数。

三丶线程的同步:

1丶问题的引入:

什么线程的同步?

即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作。

我们可以举例说明一下这个问题:

假如在我们抢火车票的时候,有好多人同时抢火车票,这时候我们就会发现如果只剩下一张票,抢票的人几乎同时抢这一张票会不会都拥有这张票?
在我们平常的生活当中,其实是完全不会的,不可能实现的。而我们避免这样的问题就出现就要实现线程的同步。

例:

public class demo06 {public static void main(String[] args) {windows2 w=new windows2();w.setName("窗口1");w.start();windows2 w1=new windows2();w1.setName("窗口2");w1.start();windows2 w2=new windows2();w2.setName("窗口3");w2.start();}
}class windows2 extends Thread {private  int ticket = 100;@Overridepublic void run() {while (true) {if (ticket > 0) {System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);ticket--;} else {break;}}}
}

运行结果:

我们可以看见,如果我们这样运行我们所写的代码,会发现,其实他售票的不是100张而是三百张!
因为我们每运行一条线程我们就会就会new一个对象,而每个对象中类的属性都是单独存在自己类中的。所以我们需要将他们共享:static。修改之后: private static int ticket = 100;

运行结果:

虽然我们在这个时候进行改进可还是会出现重票的情况,也就是出现了线程安全的问题。
所以我们需要进行线程的同步。

我们可以看见当其中的一条线程进行执行的时候,其他线程也参与了进来。

出现这个问题的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。简单的来说我们要解决这样的问题就要在其中的一条线程执行的时候,其他线程不要参与。

我们可以有这样的思路:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。(其实这个也就是线程的同步机制)

2丶同步代码块解决:

1丶格式:

synchronized(同步监视器){
//需要被同步的代码
}

1丶操作共享数据的代码,即为需要被同步的代码。(不能包含代码多了,也不能包含代码少了。)
2丶共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
3丶同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
要求:多个线程必须要共用同一把锁

2丶Runnable接口创建多线程的方式中解决:

public class demo04 {public static void main(String[] args) {windows w1=new windows();Thread thread1 = new Thread(w1);thread1.setName("窗口1");thread1.start();Thread thread2 = new Thread(w1);thread1.setName("窗口1");thread2.start();Thread thread3 = new Thread(w1);thread1.setName("窗口1");thread3.start();}}class windows implements Runnable{private static int ticket = 100;Object object=new Object();@Overridepublic void run() {while (true) {synchronized (object) {if (ticket >0) {System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);ticket--;} else {break;}}}}
}

运行结果:

我们的同步监视器,也就是锁。任何一个类的对象,都可以充当锁,那与其不如让他自己本类的对象来充当锁,也就是this 。但是这个不可以在继承的方式中实现,我们试想,如果在继承的方法中实现,我们每创建一次对象,这个this就代表着不同的对象,对象不唯一,那么锁就不唯一,线程就不安全。
而在接口中实现的,只是创建实现类这一个对象。

2丶继承Thread类创建多线程的方式中解决:

public class demo06 {public static void main(String[] args) {windows2 w=new windows2();windows2 w1=new windows2();windows2 w2=new windows2();w.setName("窗口1");w.start();w1.setName("窗口2");w1.start();w2.setName("窗口3");w2.start();}
}class windows2 extends Thread {private static int ticket = 100;static Object object=new Object();@Overridepublic void run() {while (true) {synchronized (object) {if (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);ticket--;} else {break;}}}}
}

运行结果:

在这里我们可以看到上面的两周同步代码的方式是解决了这个问题。

假如在继承的解决方式中我们用this充当锁:

public class demo06 {public static void main(String[] args) {windows2 w=new windows2();windows2 w1=new windows2();windows2 w2=new windows2();w.setName("窗口1");w.start();w1.setName("窗口2");w1.start();w2.setName("窗口3");w2.start();}
}class windows2 extends Thread {private static int ticket = 100;@Overridepublic void run() {while (true) {synchronized (this) {if (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);ticket--;} else {break;}}}}
}

运行结果:

所以千万不可以这样写。

总结下来:

在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。 在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。

3丶同步方法解决:

1丶Runnable接口:

public class demo07 {public static void main(String[] args) {windows3 w=new windows3();Thread thread = new Thread(w);thread.setName("窗口1");thread.start();Thread thread1 = new Thread(w);thread1.setName("窗口2");thread1.start();Thread thread2 = new Thread(w);thread2.setName("窗口3");thread2.start();}
}class windows3 implements Runnable{private static int ticket = 100;@Overridepublic void run() {show();}private synchronized void show(){while (true) {if (ticket > 0) {System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);ticket--;} else {break;}}}}

2丶继承Thread

public class demo08 {public static void main(String[] args) {windows4 w=new windows4();windows4 w1=new windows4();windows4 w2=new windows4();w.setName("窗口1");w.start();w1.setName("窗口2");w1.start();w2.setName("窗口3");w2.start();}
}
class windows4 extends Thread{private static int ticket = 100;@Overridepublic void run() {show();}private static synchronized void show(){while (true) {if (ticket > 0) {System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);ticket--;} else {break;}}}
}

其实解决的方法大同小异,值得注意的是,在用继承的方法实现的时候一定要将方法用static修饰。因为我们每调用一次执行start()都会单独执行一次show()方法,所以我们将show()方法用static进行修饰。

4丶Lock解决:

从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。

java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。

ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

例:

public class demo09 {public static void main(String[] args) {windows5 w=new windows5();Thread thread = new Thread(w);Thread thread1 = new Thread(w);Thread thread2 = new Thread(w);thread.start();thread1.start();thread2.start();}
}
class windows5 implements Runnable {private static int ticket = 100;private ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {while (true) {try {lock.lock();if (ticket > 0) {System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);ticket--;} else {break;}}finally {lock.unlock();}}}
}

运行结果:

我们可以看见这个方法也解决了。

但是这个新方法究竟和synchronizeded有什么异同呢?

相同:二者都可以解决线程安全问题
不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器 Lock需要手动的启动同步(lock(),同时结束同步也需要手动的实现(unlock())

四丶死锁的问题:

1丶死锁的理解:

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁

我们来看这样的一段代码:

public class ThreadTest {public static void main(String[] args) {StringBuffer s1 = new StringBuffer();StringBuffer s2 = new StringBuffer();new Thread(){@Overridepublic void run() {synchronized (s1){s1.append("a");s2.append("1");synchronized (s2){s1.append("b");s2.append("2");System.out.println(s1);System.out.println(s2);}}}}.start();new Thread(new Runnable() {@Overridepublic void run() {synchronized (s2){s1.append("c");s2.append("3");synchronized (s1){s1.append("d");s2.append("4");System.out.println(s1);System.out.println(s2);}}}}).start();}}

运行结果:
.
我们分析一下这种代码时的情况:

先开始先进入上面的那个线程,之后再进行字符串的添加,线程先握住了s1,要想进入下一段代码的执行,就要握住s2。最后释放锁,下一个线程开始执行同样的操作。

我们在看看下面的这行代码:

public class ThreadTest {public static void main(String[] args) {StringBuffer s1 = new StringBuffer();StringBuffer s2 = new StringBuffer();new Thread(){@Overridepublic void run() {synchronized (s1){s1.append("a");s2.append("1");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (s2){s1.append("b");s2.append("2");System.out.println(s1);System.out.println(s2);}}}}.start();new Thread(new Runnable() {@Overridepublic void run() {synchronized (s2){s1.append("c");s2.append("3");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (s1){s1.append("d");s2.append("4");System.out.println(s1);System.out.println(s2);}}}}).start();}}


这是为什么呢?

我们来分析一下:
当我们在进行第一段线程的时候,握住了s1这个所之后线程阻塞。下面的线程开始,握住了s2,线程开始阻塞。可是在这个时候,我们如果想要程序继续执行,就要释放锁,第一个线程需要s2,第二个线程需要s1,但是由于线程阻塞,锁并没有释放,才会让两个线程相互等待,出现死锁的问题。

我们可以形象的举一个例子:就像是一对彼此相爱的男生和女生,双方都期待着对方的表白,但是始终没有说出口,就这样一直耗着,这两个人最后没有在一起。这个例子也就是我们所说的死锁问题了。

五丶线程的通信:

1丶线程的通信

线程的通信:为了更好的协作,线程无论是交替式执行,还是接力式执行,都需要进行通信告知

线程的通信看上去很高尚,其实呢,就是几个关于线程方法的使用。
例:

public class demo01 {public static void main(String[] args) {sum s=new sum();Thread thread1=new Thread(s);Thread thread2=new Thread(s);thread1.setName("线程1");thread2.setName("线程2");thread1.start();thread2.start();}
}class sum implements Runnable {private static int number = 1;@Overridepublic void run() {while (true) {synchronized (this){if (number <=100) {System.out.println(Thread.currentThread().getName() + ":"+number);number++;}else {break;}}}
}
}

运行结果:

这是一段很普通的线程安全的代码从一加到100,如果我们想要交互进行输出,那么只能进行现成的通信。

例:

public class demo01 {public static void main(String[] args) {sum s=new sum();Thread thread1=new Thread(s);Thread thread2=new Thread(s);thread1.setName("线程1");thread2.setName("线程2");thread1.start();thread2.start();}
}class sum implements Runnable {private static int number = 1;Object object=new Object();@Overridepublic void run() {while (true) {synchronized (object){object.notify();if (number <=100) {System.out.println(Thread.currentThread().getName() + ":"+number);number++;try {object.wait();} catch (InterruptedException e) {e.printStackTrace();}}else {break;}}}
}
}

运行结果:

我们可以看见在这里我们用到了两个新的方法:notify();和wait();方法。

wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高个。
notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。

所以我们在执行的时候线程1线进入,并握住synchronized的锁,当我们执行到wait()这个方法的时候,线程开始阻塞,这时候线程2进入唤醒是线程1并释放同步监视器(锁),之后线程执行wait()就这样重复的进行,就实现了线程的通信。

我们需要注意的是:wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中都定义在java.lang.Object类中,并且在调用者三个方法的时候一定要试用同步代码块和同步方法中的同步监视器。

public class demo01 {public static void main(String[] args) {sum s=new sum();Thread thread1=new Thread(s);Thread thread2=new Thread(s);thread1.setName("线程1");thread2.setName("线程2");thread1.start();thread2.start();}
}class sum implements Runnable {private static int number = 1;Object object=new Object();@Overridepublic void run() {while (true) {synchronized (this){object.notify();if (number <=100) {System.out.println(Thread.currentThread().getName() + ":"+number);number++;try {object.wait();} catch (InterruptedException e) {e.printStackTrace();}}else {break;}}}
}
}

运行结果:

像这样我将同步监视器改为本类对象this,但是用object调用,就会爆出异常。

看到这里我们可能会想到,sleep()和wait()同样是可以让线程阻塞,这两个方法有什么区别呢?

1丶相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
2丶不同点:
1)两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
2)调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。

六丶总结:

今天整理的java多线程。希望大家能学到,同时也是自己重温知识的过程,我每天都会整理javase的基础知识分享给大家,完善自己。如果哪方面有问题,还请指正,期待大家的评论和关注,最后送给大家也送给自己一句话:真正的大师永远都怀着一颗学徒的心。

                                                                                              ——我是kdy丶,一个未来的java程序员

舔砖加瓦(java)之多线程相关推荐

  1. 舔砖加瓦(java)之java面向对象

    添砖加瓦(java) java面向对象 前言: 大家好我是kdy丶 文章目录 添砖加瓦(java) ***java面向对象*** 前言: 一丶面向对象与面向过程: 1丶面向对象与面向过程的区别: 2丶 ...

  2. 舔砖加瓦(java)之java常用类

    添砖加瓦(java) 常用类 前言: 大家好我是kdy丶 这里写目录标题 添砖加瓦(java) ***常用类*** 前言: 一丶字符串相关的类 1丶String类 2丶StringBuffer和Str ...

  3. 舔砖加瓦(java)之异常

    添砖加瓦(java) java异常 前言: 大家好我是kdy丶 这里写目录标题 添砖加瓦(java) ***java异常*** 前言: 一丶异常的概述和体系结构: 1丶异常的概述: 2丶异常的体系结构 ...

  4. 大王java_加瓦java大王

    ------- android培训.java培训.期待与您交流! ---------- 学了一段时间Jvava了,我怎么发现java里面的程序都还不可以接收键盘输入的信息呢,我就记得以前学c语言的时候 ...

  5. Java 并发/多线程教程(四)-并发模型

    本系列译自jakob jenkov的Java并发多线程教程(本章节部分内容参考http://ifeve.com/并发编程模型),个人觉得很有收获.由于个人水平有限,不对之处还望矫正! 并发系统可以有多 ...

  6. 关于Java的多线程Runnable的个人理解(基础,不讲概念)

    背景说明: 在学了Java的多线程(继承Thread,Runnable)以后,我出于好奇,就想知道java到底是不是多线程的,不能它说自己是多线程就是多线程,自己想验证一下,于是我就想测试一下,但继承 ...

  7. java基础-多线程应用案例展示

    java基础-多线程应用案例展示 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.两只熊,100只蜜蜂,蜜蜂每次生产的蜂蜜量是1,罐子的容量是30,熊在罐子的蜂蜜量达到20的时候 ...

  8. Java的多线程机制系列:(四)不得不提的volatile及指令重排序(happen-before)

    一.不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它:我们在JDK及开源框架中随处可见这个关键字,但并发专 ...

  9. 网络编程--JAVA之多线程下载后续:断点续存

    这篇博客就是接在我上篇博客网络编程–JAVA之多线程下载的基础上来实现的. 首先,我来说一下断点续存能解决啥问题: 假如当我们在进行MP4下载时,如果突然出现人为的中断或者意外的中断,那么当我们再次点 ...

最新文章

  1. python 安装使用saltstack salt-api 简介
  2. html中选择收货地址时候,收货地址.html
  3. 云端TensorFlow读取数据IO的高效方式
  4. 001.Amoeba读写分离部署
  5. apache spark_Apache Spark Job的剖析
  6. java 连接ldap_ldap java 连接demo
  7. js 操作table: insertRow(),deleteRow(),insertCell(),deleteCell()方法
  8. Linux系统下xampp集成环境安装
  9. java 双分派_双分派 和 访问者模式详解 | 学步园
  10. 不小心合并了icloud通讯录_苹果手机通讯录突然不见了如何恢复呢?
  11. Linux命令详解词典
  12. 2019南昌市计算机教师招聘,南昌民德学校2019年教师招聘公告
  13. ZZULIOJ 1065 统计数字字符的个数
  14. 5G仿真-蒙特卡洛仿真方法
  15. 毕业论文如何设置页码连续编页,页眉奇偶页不同?
  16. 《燃点》-- 星星之火可以燎原
  17. JQuery替换元素
  18. DB SQL mysql
  19. 一行代码完成英文单词首字母大写转换,ABC、abc、aBC转换为Abc
  20. opendns_如何使用OpenDNS设置全屋家长控制

热门文章

  1. Python Flask Web教程002:Flask 快速上手
  2. 安卓6.0版本后出现的语音开启失败问题,错误码20006
  3. HashMap的链表结构
  4. HTTP 有哪些方法?
  5. Excel如何快速将图片插入到批注中?
  6. 肖战真的没我帅!我自己写的Python颜值检测说的!
  7. HTML5基础:布局和标签
  8. java 读写 wps xlsx 文件
  9. 防火墙基础配置(二)
  10. 计算机打字题数字知识,电脑打字出现的是数字怎么办