Java CountDownLatch 和 CyclicBarrier 示例

复习了一下 JCIP 回顾一下同步工具类的使用

CountDownLatch


import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CountDownLatchExample1 {

	private final static int threadCount = 200;

	public static void main(String[] args) throws Exception {

		ExecutorService exec = Executors.newCachedThreadPool();

		final CountDownLatch countDownLatch = new CountDownLatch(threadCount);

		for (int i = 0; i < threadCount; i++) {
			final int threadNum = i;
			exec.execute(() -> {
				try {
					test(threadNum);
				} catch (Exception e) {
					e.printStackTrace();
				} finally {
					countDownLatch.countDown();
				}
			});
		}
		countDownLatch.await();
		System.out.println("finish");
		exec.shutdown();
	}

	private static void test(int threadNum) throws Exception {
		Thread.sleep(100);
		System.out.println("{" + threadNum + "}");
		Thread.sleep(100);
	}
}

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class CountDownLatchExample2 {
	private final static int threadCount = 200;

	public static void main(String[] args) throws Exception {

		ExecutorService exec = Executors.newCachedThreadPool();

		final CountDownLatch countDownLatch = new CountDownLatch(threadCount);

		for (int i = 0; i < threadCount; i++) {
			final int threadNum = i;
			exec.execute(() -> {
				try {
					test(threadNum);
				} catch (Exception e) {
					e.printStackTrace();
				} finally {
					countDownLatch.countDown();
				}
			});
		}
                //超时之后不阻塞,直接继续运行
		countDownLatch.await(10, TimeUnit.MILLISECONDS);
		System.out.println("finish");
		exec.shutdown();
	}

	private static void test(int threadNum) throws Exception {
		Thread.sleep(100);
		System.out.println("{" + threadNum + "}");
	}
}
import java.util.concurrent.CountDownLatch;

public class TestCountDownLatch {

	public long timeTasks(int nThreads, final Runnable task) throws InterruptedException {

		final CountDownLatch startGate = new CountDownLatch(1);
		final CountDownLatch endGate = new CountDownLatch(nThreads);

		for (int i = 0; i < nThreads; i++) {
			Thread thread = new Thread() {
				@Override
				public void run() {
					try {
						startGate.await();
						try {
							task.run();
						} finally {
							endGate.countDown();
						}
					} catch (InterruptedException e) {

					}

				}
			};
			thread.start();
		}

		long start = System.nanoTime();
		startGate.countDown();
		endGate.await();
		long end = System.nanoTime();
		return end - start;
	}

	public static void main(String[] args) {
		int nTask = 10;
		TestCountDownLatch testCountDownLatch = new TestCountDownLatch();
		try {
			long timeUse = testCountDownLatch.timeTasks(nTask, new Task());
			System.out.println(nTask + " use time " + timeUse);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

	}

}

class Task implements Runnable {

	@Override
	public void run() {
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

CyclicBarrier

import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CyclicBarrierExample1 {

	private static CyclicBarrier barrier = new CyclicBarrier(5);

	public static void main(String[] args) throws Exception {

		ExecutorService executor = Executors.newCachedThreadPool();

		for (int i = 0; i < 10; i++) {
			final int threadNum = i;
			Thread.sleep(1000);
			executor.execute(() -> {
				try {
					race(threadNum);
				} catch (Exception e) {
					e.printStackTrace();
				}
			});
		}
		executor.shutdown();
	}

	private static void race(int threadNum) throws Exception {
		Thread.sleep(1000);
		System.out.println("{" + threadNum + "} is ready ");
		barrier.await();
		System.out.println("{" + threadNum + "} continue ");
	}
}
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class CyclicBarrierExample2 {
	private static CyclicBarrier barrier = new CyclicBarrier(5);

	public static void main(String[] args) throws Exception {

		ExecutorService executor = Executors.newCachedThreadPool();

		for (int i = 0; i < 10; i++) {
			final int threadNum = i;
			Thread.sleep(1000);
			executor.execute(() -> {
				try {
					race(threadNum);
				} catch (Exception e) {
					e.printStackTrace();
				}
			});
		}
		executor.shutdown();
	}

	private static void race(int threadNum) throws Exception {
		Thread.sleep(1000);
		System.out.println("{" + threadNum + "} is ready");
		try {
                        //等待超时之后直接放开栅栏,让线程执行,不需要等到规定线程数都就位
			barrier.await(2000, TimeUnit.MILLISECONDS);
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println("{" + threadNum + "} continue");
	}
}
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CyclicBarrierExample3 {
        //开始之前执行回调函数
	private static CyclicBarrier barrier = new CyclicBarrier(5, () -> {
		System.out.println("callback is running");
	});

	public static void main(String[] args) throws Exception {

		ExecutorService executor = Executors.newCachedThreadPool();

		for (int i = 0; i < 10; i++) {
			final int threadNum = i;
			Thread.sleep(1000);
			executor.execute(() -> {
				try {
					race(threadNum);
				} catch (Exception e) {
					e.printStackTrace();
				}
			});
		}
		executor.shutdown();
	}

	private static void race(int threadNum) throws Exception {
		Thread.sleep(1000);
		System.out.println("{" + threadNum + "} is ready");
		barrier.await();
		System.out.println("{" + threadNum + "} continue");
	}
}

C 语言字符串操作

#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <ctype.h>

char *strupr_t(char *str)
{
	char *orign=str;
	for (; *str!='\0'; str++)
		*str = toupper(*str);
	return orign;
}

char *strlowr_t(char *str)
{
	char *orign=str;
	for (; *str!='\0'; str++)
		*str = tolower(*str);
	return orign;
}

//字符串拷贝(strcpy, strncpy)
void testStrCpy(){
	char dest[1024] = {0};
	char *str = "abcde";
	//strcpy()函数会将源字符串中的结束符('\0')也拷贝到目的字符串中。
    //注意,strcpy()可能会导致溢出。
	strcpy(dest,str);

	printf("%s\n", dest);


	char dest2[7] = {0};
	char *src2 = "abcde";
	dest2[5] = 'A';
	//该函数从源字符串中拷贝n个字符到目的字符串;如果源字符串长度不足,则用 NULL 填充,以保证将n个字符写入目的字符串中;如果源字符串中前n个字符不包含字符串结束符,函数不会为目的字符串添加上结束符。
	//所以如果有需要,应该在拷贝后自己在目的字符串尾部添加结束符。
	strncpy(dest2, src2, 5);
	printf("%s\n", dest2);
}

void testStrCmp()
{
	char *s11 = "abcde";
	char *s12 = "abcef";
	char *s13 = "ad";
	//从这个结果可以发现,strcmp()是根据字典序来对字符串进行比较的。进一步的,还可以发现strcmp()的返回值是比较过程中最后一次比较时两个字符的值的差
	printf("compare(%s, %s) -> %d\n", s11, s12, strcmp(s11, s12));
	printf("compare(%s, %s) -> %d\n", s12, s13, strcmp(s12, s13));
	printf("compare(%s, %s) -> %d\n", s11, s13, strcmp(s11, s13));

	char *s21 = "abcde";
	char *s22 = "abcfg";
	char *s23 = "ad";
	//和strcmp()的区别是,strncmp()只对s1和s2的前n个字节进行比较。
	printf("compare(%s, %s, 4) -> %d\n", s21, s22, strncmp(s21, s22, 4));
	printf("compare(%s, %s, 2) -> %d\n", s21, s23, strncmp(s21, s23, 2));
	printf("compare(%s, %s, 3) -> %d\n", s22, s23, strncmp(s22, s23, 3));

	char *s31 = "AbcdE";
	char *s32 = "abcdE";
    //使用strcasecmp()应该包含 strings.h 而不是 string.h
    //strcasecmp()在比较时不区分大小写
    //strncasecmp()之于strcasecmp()就如strncmp()之于strcmp()
	printf("compare(%s, %s) with case -> %d\n", s31, s32, strcmp(s31, s32));
	printf("compare(%s, %s) ignore case -> %d\n", s31, s32, strcasecmp(s31, s32));
}

void testStrCat()
{
	char dest[1024] = "hello ";
	char *src = "world!";
	//strcat()首先会覆盖掉目的字符串的结束符,然后把源字符串的内容追加到后面,并在最后添加结束符。如果目的字符串缓冲区长度不够,将导致溢出。
    //strcat()在操作完成后,返回目的字符串的首地址,这样可以方便地进行链式操作。
	printf("%s\n", strcat(dest, src));

	char dest2[1024] = "hello ";
	char *src2 = "world!kkkkaaaa";
	//strncat()将最多n个字节的内容追加到目的字符串尾部,并且会在追加后添加终止符号。
    //同strcat()一样,它返回目的字符串的首地址。
	printf("%s\n", strncat(dest2, src2,6));
}

void testStrSearch()
{
	char *s1 = "hello world!";
	char c = 'l';
	//strchr()返回一个字符指针,指向指定字符在指定字符串中第一次出现的位置。如果在指定字符串中没有找到指定字符,则返回 NULL 。该函数的第二个参数按理来说应当是一个字符,不过标准库中确实是int类型。
	printf("%s\n", strchr(s1, c));
	//strrchr()和strchr()类似,但它返回的是指定字符在指定字符串中最后一次出现的位置。如果未找到,同样返回 NULL 。
	printf("%s\n", strrchr(s1, c));
	//strchrnul()的功能和strchr()只有细微的区别,那就是,当没有找到指定字符时,strchrnul()不返回 NULL ,而是返回字符串结束符的位置。
	//这里由于strchrnul()的特性,没办法通过打印字符串来了解strchrnul()的操作
	//'strchrnull' is invalid in C99
	//printf("%p, %p\n", s1, strchrnull(s1, 'f'));

	char *accept = "wo";
	//strpbrk()和strchr()的区别在于,strchr()是从字符串里搜索 一个字符 ,而strpbrk()则是在字符串里搜索 一个字符集中的字符 ,看第二个参数就明白了。strpbrk()遍历字符串,如果发现某个字符在指定的 字符集 中,则立即返回指向该字符的指针。如果最后没有找到任何在指定字符集中的字符,则返回 NULL 。
	printf("%s\n", strpbrk(s1, accept));
}

void testStrSplit()
{
	char s[1024] = "abc;lsdk:lskdj,;slsj";
	char *delm = ";:,";
	char *result = NULL;
	int len = strlen(s);
	int i = 0;
    //strtok()根据第二个参数指定的分隔符(可能存在多个不同的分隔符)将指定字符串分割成多个子串。通过多次调用strtok(),可以依次获得字符串的多个子串的首地址。要注意的是,除了第一次调用时将待分割字符串作为第一个参数,后续的调用要将第一个参数置为 NULL 。当字符串已经无法再分割时,strtok()返回 NULL 。
    //除了上面说过的strtok()的用法外,还要注意的是,作为待分割的字符串,它必须是 可更改的 。否则虽然可以通过编译,但运行会出错。要理解这个现象,首先要了解strtok()的内部机制。
	result = strtok(s, delm);
	while (result != NULL) {
		printf("Source:%s, Sub:%s\n", s, result);
		result = strtok(NULL, delm);
	}
}

/*
T	o	 	b	e	 	o	r	 	n	o	t	 	t	o	 	b	e
84	111	32	98	101	32	111	114	32	110	111	116	32	116	111	32	98	101
84	111	0	98	101	32	111	114	32	110	111	116	32	116	111	32	98	101
84	111	0	98	101	0	111	114	32	110	111	116	32	116	111	32	98	101
84	111	0	98	101	0	111	114	0	110	111	116	32	116	111	32	98	101
84	111	0	98	101	0	111	114	0	110	111	116	0	116	111	32	98	101
84	111	0	98	101	0	111	114	0	110	111	116	0	116	111	0	98	101
84	111	0	98	101	0	111	114	0	110	111	116	0	116	111	0	98	101

可以看到,s中的分隔符,逐次地被置为'\0'即字符串结束符。这就是strtok()分割字符串的内部原理了。而strtok()返回的指针,其实就是s中各个子串的起始位置了。如果s指向的内容是无法被修改的,那么strtok()自然也就无法将原先的分隔符置为字符结束符了。

当然了,由于源字符串会被修改,在实际中,如果需要,可以用strdup()来建立一个源字符串的副本。
*/
void testStrSplit2()
{
	char s[64] = "To be or not to be";
	char *delm = " ";
	char *result = NULL;
	int i =0,len = strlen(s);
	for (i =0;i<len;i++){
		printf("%c ", s[i]);
	}
	printf("\n");
	for (i=0;i<len;i++){
		printf("%d ",(int)s[i]);
	}
	printf("\n");

	result = strtok(s, delm);

	while (result != NULL){
		for (i=0;i<len;i++){
			printf("%d ",(int)s[i]);
		}
		printf("\n");
		result = strtok(NULL, delm);
	}
}

void testStrSplit3()
{
	char s[64] = "Hello World";
	char *delm = " ";
	char *result = NULL, *ptr = NULL;

	printf("Source:%p\n", s);
	//char *strtok_r(char *str, const char *delim, char **saveptr);
	//strtok_r()是Linux下的strtok()的可重入版本(线程安全版本),它比strtok()多了一个参数 saveptr ,这个参数用于在分割字符串时保存上下文。
	result = strtok_r(s, delm, &ptr);

	while (result != NULL){
		printf("Result:%p\t", result);
		printf("Saveptr:%p\t", ptr);
		printf("---%s\t", result);
		printf("---%s\n", ptr);
		//可以看到,saveptr这个指针在每次调用strtok_r()后就指向了未分割的部分的首地址。相对地,strtok()则是在内部有一个静态缓冲区,通过这个静态缓冲区来记录未处理的起始位置,所以strtok()不是线程安全的。
		result = strtok_r(NULL, delm, &ptr);
	}

}

//因为和strtok()的这个不同之处,strsep不需要区分第一次调用后后续的连续调用,可以用统一的操作来对字符串进行分割。
void testStrSplit4()
{
	char s[64] = "To be or not to be";
	char *source = s;
	char *delm = " ";
	char *result = NULL;

	while (source != NULL){
		printf("Source: %s | ", source);
		result = strsep(&source,delm);
		printf("result: %s | ", result);
	}
}

void testStrMatch()
{
	char *s = "To be or not to be.";
	char *p = "be";
	//strstr()返回字符串needle在字符串haystack中第一次出现的位置;如果没有匹配,则返回 NULL 。
	printf("%s\n", strstr(s, p));
}

void testStrDup()
{
	char *s = "aabbccddee";
	char *dup = strdup(s);
	//strdup()调用malloc()分配一块内存并将字符串s的内容拷贝进去,产生s的副本。要注意的是,在最后应该调用free()来释放副本。
	printf("%s\n",dup);
	free(dup);

	char *s1 = "poiuytrewq";
	//strndup()和strdup()类似,但最多只拷贝s的前n个字节。如果s的长度大于n,还会在副本后添加终止符。
	char *dup1 = strndup(s1, 4);
	printf("%s\n",dup1);
	free(dup1);

	//strdupa()和strdup()类似,但在分配内存时,它使用alloca()而不是malloc()。
	//strndupa()之于strdupa()就如strndup()之于strdup(),不再赘述。
}

/*
函数名: swab
功  能: 交换字节
用  法: void swab (char *from, char *to, int nbytes);
*/
void testStringSwab()
{
	char source[15] = "rFna koBlrna d";
	char target[15];

	swab(source,target,strlen(source));
	printf("%s\n",target);
}

/*
函数名: strupr
功  能: 将串中的小写字母转换为大写字母
用  法: char *strupr(char *str);
*/
void testStrUpper()
{
	char string[100] = "abcdefghijklmnopqrstuvwxyz";
	char *ptr;

   /* converts string to upper case characters */
	ptr = strupr_t(string);
	printf("%s\n", ptr);
}

/*
函数名: strtol
功  能: 将串转换为长整数
用  法: long strtol(char *str, char **endptr, int base);
*/
void testStrtol()
{
	char *string = "87654321", *endptr;
	long lnumber;

   /* strtol converts string to long integer  */
	lnumber = strtol(string, &endptr, 10);
	printf("string = %s  long = %ld\n", string, lnumber);
}

/*
函数名: strtod
功  能: 将字符串转换为double型值
用  法: double strtod(char *str, char **endptr);
*/
void testStrtod()
{
	char input[80], *endptr;
	double value;

	printf("Enter a floating point number:");
	gets(input);
	value = strtod(input, &endptr);
	printf("The string is %s the number is %lf\n", input, value);
}

/*
函数名: strspn
功  能: 在串中查找指定字符集的子集的第一次出现
用  法: int strspn(char *str1, char *str2);
*/
void testStrspan()
{
	char *string1 = "1234567890";
	char *string2 = "123DC8";
	int length;

	length = strspn(string1, string2);
	printf("Character where strings differ is at position %d\n", length);
}


/*
函数名: strset
功  能: 将一个串中的所有字符都设为指定字符
用  法: char *strset(char *str, char c);
*/
// void testStrset()
// {
// 	char string[10] = "123456789";
// 	char symbol = 'c';

// 	printf("Before strset(): %s\n", string);
// 	strset(string, symbol);
// 	printf("After strset():  %s\n", string);

// }

/*
函数名: strnset
功  能: 将一个串中的n个字符都设为指定字符
用  法: char *strnset(char *str, char ch, unsigned n);
*/
// void testStrnset()
// {
// 	char *string = "abcdefghijklmnopqrstuvwxyz";
// 	char letter = 'x';

// 	printf("string before strnset: %s\n", string);
// 	strnset(string, letter, 13);
// 	printf("string after  strnset: %s\n", string);
// }

void strrev(char *head)
{
  if (!head) return;
  char *tail = head;
  // find the 0 terminator, like head+strlen
  while(*tail) ++tail;
  // tail points to the last real char
  --tail;               
  // head still points to the first
  for( ; head < tail; ++head, --tail) {
      // walk pointers inwards until they meet or cross in the middle
      char h = *head, t = *tail;
      *head = t;           // swapping as we go
      *tail = h;
  }
}

void reverse(char s[])
{
    int length = strlen(s) ;
    int c, i, j;

    for (i = 0, j = length - 1; i < j; i++, j--)
    {
        c = s[i];
        s[i] = s[j];
        s[j] = c;
    }
}

/*
函数名: strrev
功  能: 串倒转
用  法: char *strrev(char *str);
*/
void testStrrev()
{
	char forward[15] = "string";

	printf("Before strrev(): %s\n", forward);
	strrev(forward);
	printf("After strrev():  %s\n", forward);
	reverse(forward);
	printf("After reverse():  %s\n", forward);
}


/*
函数名: strcspn
功  能: 在串中查找第一个给定字符集内容的段
用  法: int strcspn(char *str1, char *str2);
*/
void testStrcspn()
{
	char *string1 = "1234567890";
	char *string2 = "747DC8";
	int length;

	length = strcspn(string1, string2);
	printf("Character where strings intersect is at position %d\n", length);
}



/*
atof(将字符串转换成浮点型数)
atoi(将字符串转换成整型数)
atol(将字符串转换成长整型数)
strtod(将字符串转换成浮点数)
strtol(将字符串转换成长整型数)
strtoul(将字符串转换成无符号长整型数)
toascii(将整型数转换成合法的ASCII 码字符)
toupper(将小写字母转换成大写字母)
tolower(将大写字母转换成小写字母)
atof(将字符串转换成浮点型数)
*/

int main(int argc, char *argv[])
{
	testStrCpy();
	testStrCmp();
	testStrCat();
	testStrSearch();
	testStrSplit();
	testStrSplit2();
	testStrSplit3();
	testStrSplit4();
	testStrMatch();
	testStrDup();

	testStrspan();
	// testStrset();
	// testStrnset();
	testStrrev();
	testStrcspn();
	testStrtod();
	testStrtol();
	testStrUpper();
	return 0;
}

How do I install Java on Mac OSX allowing version switching?

原文链接

note: These solutions work for various versions of Java including Java 8 and the new Java 13, and for any other previous Java version covered by the listed version managers. This includes alternative JDK’s from OpenJDK, Oracle, IBM, Azul, Amazon Correto, Graal and more. Easily work with Java 7, Java 8, Java 9, Java 10, Java 11, Java 12, and Java 13!

You have a few options of how to do the installation as well as manage JDK switching. Installation can be done by Homebrew, SDKMANJabba, or a manual install. Switching can be done by JEnvSDKMANJabba, or manually by setting JAVA_HOME. All of these are described below.


Installation

First, install Java using whatever method you prefer including Homebrew, SDKMAN or a manual install of the tar.gz file. The advantages of a manual install is that the location of the JDK can be placed in a standardized location for Mac OSX.

Install with SDKMAN

This is a simple model in that it handles both installation and version switching, with a caveat that it installs the JDK into a non-standard directory.

<see below “Installing and Switching versions with SDKMAN”>

Install using Jabba

This is also a simple model in that both installation and version switching are handled by the same tool. The installations are made to a non-standard directory.

<see below “Installing and Switching versions with Jabba”>

Install manually from OpenJDK download page:

  1. Download OpenJDK for Mac OSX from http://jdk.java.net/ (for example Java 13)
  2. Unarchive the OpenJDK tar, and place the resulting folder (i.e. jdk-13.jdk) into your /Library/Java/JavaVirtualMachines/ folder since this is the standard and expected location of JDK installs. You can also install anywhere you want in reality.

Install with Homebrew

The version of Java available in Homebrew Cask previous to October 3, 2018 was indeed the Oracle JVM. Now however, it has now been updated to OpenJDK. Be sure to update Homebrew and then you will see the lastest version available for install.

  1. install Homebrew if you haven’t already. Make sure it is updated:
    brew update
  2. Add the casks tap, if you haven’t already (or you are not seeing older Java versions anymore with step #3):
    brew tap homebrew/cask-versions

    and for the AdoptOpenJDK versions, add that tap:

    brew tap adoptopenjdk/openjdk

    These casks change their Java versions often, and there might be other taps out there with additional Java versions.

  3. Look for installable versions:
    brew search java   

    or for AdoptOpenJDK versions:

    brew search jdk     
  4. Check the details on the version that will be installed:
    brew cask info java

    or for the AdoptOpenJDK version:

    brew cask info adoptopenjdk
  5. Install a specific version of the JDK such as java11adoptopenjdk8, or just java or adoptopenjdk for the current. For example:
    brew cask install java

    You can use the fully qualified path to older versions as well:

    brew cask install homebrew/cask-versions/java11

And these will be installed into /Library/Java/JavaVirtualMachines/ which is the traditional location expected on Mac OSX.

Other installation options:

Some other flavours of openJDK are:

Azul Systems Java Zulu certified builds of OpenJDK can be installed by following the instructions on their site.

Zulu® is a certified build of OpenJDK that is fully compliant with the Java SE standard. Zulu is 100% open source and freely downloadable. Now Java developers, system administrators, and end users can enjoy the full benefits of open source Java with deployment flexibility and control over upgrade timing.

Amazon Correto OpenJDK builds have an easy to use an installation package for version 8 or version 11 (other versions are coming), and installs to the standard /Library/Java/JavaVirtualMachines/ directory on Mac OSX.

Amazon Corretto is a no-cost, multiplatform, production-ready distribution of the Open Java Development Kit (OpenJDK). Corretto comes with long-term support that will include performance enhancements and security fixes. Amazon runs Corretto internally on thousands of production services and Corretto is certified as compatible with the Java SE standard. With Corretto, you can develop and run Java applications on popular operating systems, including Linux, Windows, and macOS.


Where is my JDK?!?!

To find locations of previously installed Java JDK’s installed at the default system locations, use:

/usr/libexec/java_home -V

Matching Java Virtual Machines (6):
13, x86_64: “OpenJDK 13” /Library/Java/JavaVirtualMachines/openjdk-13.jdk/Contents/Home 12, x86_64: “OpenJDK 12” /Library/Java/JavaVirtualMachines/jdk-12.jdk/Contents/Home
11, x86_64: “Java SE 11” /Library/Java/JavaVirtualMachines/jdk-11.jdk/Contents/Home
10.0.2, x86_64: “Java SE 10.0.2” /Library/Java/JavaVirtualMachines/jdk-10.0.2.jdk/Contents/Home
9, x86_64: “Java SE 9” /Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home
1.8.0_144, x86_64: “Java SE 8” /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home

You can also report just the location of a specific Java version using -v. For example for Java 13:

/usr/libexec/java_home -v 13

/Library/Java/JavaVirtualMachines/jdk-13.jdk/Contents/Home

Knowing the location of the installed JDK’s is also useful when using tools like JEnv, or adding a local install to SDKMAN, or linking a system JDK in Jabba — and you need to know where to find them.

If you need to find JDK’s installed by other tools, check these locations:

  • SDKMAN installs to ~/.sdkman/candidates/java/
  • Jabba installs to ~/.jabba/jdk

Switching versions manually

The Java executable is a wrapper that will use whatever JDK is configured in JAVA_HOME, so you can change that to also change which JDK is in use.

For example, if you installed or untar’d JDK 13 to /Library/Java/JavaVirtualMachines/jdk-13.jdk if it is the highest version number it should already be the default, if not you could simply set:

export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-13.jdk/Contents/Home

And now whatever Java executable is in the path will see this and use the correct JDK.

Using the /usr/libexec/java_home utility as previously described helps you to create aliases or to run commands to change Java versions by identifying the locations of different JDK installations. For example, creating shell aliases in your .profile or .bash_profile to change JAVA_HOME for you:

export JAVA_8_HOME=$(/usr/libexec/java_home -v1.8)
export JAVA_9_HOME=$(/usr/libexec/java_home -v9)
export JAVA_10_HOME=$(/usr/libexec/java_home -v10)
export JAVA_11_HOME=$(/usr/libexec/java_home -v11)
export JAVA_12_HOME=$(/usr/libexec/java_home -v12)
export JAVA_13_HOME=$(/usr/libexec/java_home -v13)

alias java8='export JAVA_HOME=$JAVA_8_HOME'
alias java9='export JAVA_HOME=$JAVA_9_HOME'
alias java10='export JAVA_HOME=$JAVA_10_HOME'
alias java11='export JAVA_HOME=$JAVA_11_HOME'
alias java12='export JAVA_HOME=$JAVA_12_HOME'
alias java13='export JAVA_HOME=$JAVA_13_HOME'

# default to Java 13
java13

Then to change versions, just use the alias.

java8
java -version

java version “1.8.0_144”

Of course, setting JAVA_HOME manually works too!


Switching versions with JEnv

JEnv expects the Java JDK’s to already exist on the machine and can be in any location. Typically you will find installed Java JDK’s in /Library/Java/JavaVirtualMachines/. JEnv allows setting the global version of Java, one for the current shell, and a per-directory local version which is handy when some projects require different versions than others.

  1. Install JEnv if you haven’t already, instructions on the site http://www.jenv.be/ for manual install or using Homebrew.
  2. Add any Java version to JEnv (adjust the directory if you placed this elsewhere):
    jenv add /Library/Java/JavaVirtualMachines/jdk-13.jdk/Contents/Home
  3. Set your global version using this command:
    jenv global 13

You can also add other existing versions using jenv add in a similar manner, and list those that are available. For example Java 8:

jenv add /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home 
jenv versions

See the JEnv docs for more commands. You may now switch between any Java versions (Oracle, OpenJDK, other) at any time either for the whole system, for shells, or per local directory.

To help manage JAVA_HOME while using JEnv you can add the export plugin to do this for you.

$ jenv enable-plugin export
  You may restart your session to activate jenv export plugin echo export plugin activated

The export plugin may not adjust JAVA_HOME if it is already set, so you may need to clear this variable in your profile so that it can be managed by JEnv.

You can also use jenv exec <command> <parms...> to run single commands with JAVA_HOME and PATH set correctly for that one command, which could include opening another shell.


Installing and Switching versions with SDKMAN

SDKMAN is a bit different and handles both the install and the switching. SDKMAN also places the installed JDK’s into its own directory tree, which is typically ~/.sdkman/candidates/java. SDKMAN allows setting a global default version, and a version specific to the current shell.

  1. Install SDKMAN from https://sdkman.io/install
  2. List the Java versions available to make sure you know the version ID
    sdk list java
  3. Install one of those versions, for example, Java 13:
    sdk install java 13.0.0-open 
  4. Make 13 the default version:
    sdk default java 13.0.0-open

    Or switch to 13 for the session:

    sdk use java 13.0.0-open

When you list available versions for installation using the list command, you will see a wide variety of distributions of Java:

sdk list java

And install additional versions, such as JDK 8:

sdk install java 8.0.181-oracle

SDKMAN can work with previously installed existing versions. Just do a local install giving your own version label and the location of the JDK:

sdk install java my-local-13 /Library/Java/JavaVirtualMachines/jdk-13.jdk/Contents/Home

And use it freely:

sdk use java my-local-13

More information is available in the SDKMAN Usage Guide along with other SDK’s it can install and manage.

SDKMAN will automatically manage your PATH and JAVA_HOME for you as you change versions.


Installing and Switching versions with Jabba

Jabba also handles both the install and the switching. Jabba also places the installed JDK’s into its own directory tree, which is typically ~/.jabba/jdk.

  1. Install Jabba by following the instructions on the home page.
  2. List available JDK’s
    jabba ls-remote
  3. Install Java JDK 12
    jabba install openjdk@1.12.0
  4. Use it:
    jabba use openjdk@1.12.0

You can also alias version names, link to existing JDK’s already installed, and find a mix of interesting JDK’s such as GraalVM, Adopt JDK, IBM JDK, and more. The complete usage guide is available on the home page as well.

Jabba will automatically manage your PATH and JAVA_HOME for you as you change versions.

shell后台并发执行实践

shell如何在后台执行

1.nohup命令
通常我们都是远程登录linux终端,而当我们退出终端时在之前终端运行的程序都会终止,有时候先想要退出终端也要程序继续执行这时nohup就登场了。nohup命令可以将程序以忽略挂起信号的方式运行起来,被运行的程序的输出信息将不会显示到终端。
nohup command > myout.file 2>&1 &

2.&后台执行
在命令后面加 & 可以让程序在后台执行
command &

3.Ctrl + z
当一个程序正在执行并且占用当前终端时我们同时按下 Ctrl + z ,这样就会把正在执行的前台程序放到后台挂起。

并发执行

1.正常执行

#!/bin/bash
Njob=10    #任务总数
for ((i=0; i<$Njob; i++)); do
{
	echo  "progress $i is sleeping for 1 seconds zzz…"
	sleep  1
}
done
echo -e "time-consuming: $SECONDS seconds"    #显示脚本执行耗时

执行结果

progress 0 is sleeping for 1 seconds zzz…
progress 1 is sleeping for 1 seconds zzz…
progress 2 is sleeping for 1 seconds zzz…
progress 3 is sleeping for 1 seconds zzz…
progress 4 is sleeping for 1 seconds zzz…
progress 5 is sleeping for 1 seconds zzz…
progress 6 is sleeping for 1 seconds zzz…
progress 7 is sleeping for 1 seconds zzz…
progress 8 is sleeping for 1 seconds zzz…
progress 9 is sleeping for 1 seconds zzz…
-e time-consuming: 10 seconds

2.并发后台执行

#!/bin/bash
Njob=10
for ((i=0; i<$Njob; i++)); do
    echo  "progress $i is sleeping for 3 seconds zzz…"
    sleep  3 &       #循环内容放到后台执行
done
wait      #等待循环结束再执行wait后面的内容
echo -e "time-consuming: $SECONDS seconds"    #显示脚本执行耗时

执行结果

progress 0 is sleeping for 3 seconds zzz…
progress 1 is sleeping for 3 seconds zzz…
progress 2 is sleeping for 3 seconds zzz…
progress 3 is sleeping for 3 seconds zzz…
progress 4 is sleeping for 3 seconds zzz…
progress 5 is sleeping for 3 seconds zzz…
progress 6 is sleeping for 3 seconds zzz…
progress 7 is sleeping for 3 seconds zzz…
progress 8 is sleeping for 3 seconds zzz…
progress 9 is sleeping for 3 seconds zzz…
-e time-consuming: 3 seconds

这种方式从功能上实现了使用shell脚本并行执行多个循环进程,但是它缺乏控制机制。

for设置了Njob次循环,同一时间Linux就触发Njob个进程一起执行。假设for里面执行的是scp,在没有pam_limits和cgroup限制的情况下,很有可能同一时刻过多的scp任务会耗尽系统的磁盘IO、连接数、带宽等资源,导致正常的业务受到影响。

一个应对办法是在for循环里面再嵌套一层循环,这样同一时间,系统最多只会执行内嵌循环限制值的个数的进程。不过还有一个问题,for后面的wait命令以循环中最慢的进程结束为结束(水桶效应)。如果嵌套循环中有某一个进程执行过程较慢,那么整体这一轮内嵌循环的执行时间就等于这个“慢”进程的执行时间,整体下来脚本的执行效率还是受到影响的。

分批并行的方式并发执行

#!/bin/bash
NQ=3
num=5
for ((i=0; i<$NQ; i++)); do
     for ((j=0; j<$num; j++)); do
         echo  "progress $i is sleeping for 3 seconds zzz…"
        sleep 3 &
     done
     wait
 done
#等待循环结束再执行wait后面的内容
echo -e "time-consuming: $SECONDS    seconds"    #显示脚本执行耗时

执行结果

 progress 0 is sleeping for 3 seconds zzz…
 progress 0 is sleeping for 3 seconds zzz…
 progress 0 is sleeping for 3 seconds zzz…
 progress 0 is sleeping for 3 seconds zzz…
 progress 0 is sleeping for 3 seconds zzz…
 progress 1 is sleeping for 3 seconds zzz…
 progress 1 is sleeping for 3 seconds zzz…
 progress 1 is sleeping for 3 seconds zzz…
 progress 1 is sleeping for 3 seconds zzz…
 progress 1 is sleeping for 3 seconds zzz…
 progress 2 is sleeping for 3 seconds zzz…
 progress 2 is sleeping for 3 seconds zzz…
 progress 2 is sleeping for 3 seconds zzz…
 progress 2 is sleeping for 3 seconds zzz…
 progress 2 is sleeping for 3 seconds zzz…
-e time-consuming: 9    seconds

3.使用模拟队列来控制进程数量

要控制后台同一时刻的进程数量,需要在原有循环的基础上增加管理机制。

一个方法是以for循环的子进程PID做为队列元素,模拟一个限定最大进程数的队列(只是一个长度固定的数组,并不是真实的队列)。队列的初始长度为0,循环每创建一个进程,就让队列长度+1。当队列长度到达设置的并发进程限制数之后,每隔一段时间检查队列,如果队列长度还是等于限制值,那么不做操作,继续轮询;如果检测到有并发进程执行结束了,那么队列长度-1,轮询检测到队列长度小于限制值后,会启动下一个待执行的进程,直至所有等待执行的并发进程全部执行完。

#!/bin/bash
Njob=15 #任务总数
Nproc=5 #最大并发进程数

function PushQue {      #将PID值追加到队列中
           Que="$Que $1"
           Nrun=$(($Nrun+1))
}

function GenQue {       #更新队列信息,先清空队列信息,然后检索生成新的队列信息
           OldQue=$Que
           Que=""; Nrun=0
           for PID in $OldQue; do
                 if [[ -d /proc/$PID ]]; then
                        PushQue $PID
                 fi
           done
}

function ChkQue {       #检查队列信息,如果有已经结束了的进程的PID,那么更新队列信息
           OldQue=$Que
           for PID in $OldQue; do
                 if [[ ! -d /proc/$PID ]];   then
                 GenQue; break
                 fi
           done
}

for ((i=1; i<=$Njob; i++)); do
           echo "progress $i is sleeping for 3 seconds zzz…"
           sleep 3 &
           PID=$!
           PushQue $PID
           while [[ $Nrun -ge $Nproc ]]; do          # 如果Nrun大于Nproc,就一直ChkQue
                 ChkQue
                 sleep 0.1
           done
done
wait
echo -e "time-consuming: $SECONDS   seconds"    #显示脚本执行耗时

执行结果

progress 1 is sleeping for 3 seconds zzz…
progress 2 is sleeping for 3 seconds zzz…
progress 3 is sleeping for 3 seconds zzz…
progress 4 is sleeping for 3 seconds zzz…
progress 5 is sleeping for 3 seconds zzz…
progress 6 is sleeping for 3 seconds zzz…
progress 7 is sleeping for 3 seconds zzz…
progress 8 is sleeping for 3 seconds zzz…
progress 9 is sleeping for 3 seconds zzz…
progress 10 is sleeping for 3 seconds zzz…
progress 11 is sleeping for 3 seconds zzz…
progress 12 is sleeping for 3 seconds zzz…
progress 13 is sleeping for 3 seconds zzz…
progress 14 is sleeping for 3 seconds zzz…
progress 15 is sleeping for 3 seconds zzz…
-e time-consuming: 3   seconds

这种使用队列模型管理进程的方式在控制了后台进程数量的情况下,还能避免个别“慢”进程影响整体耗时的问题:

4.使用fifo管道特性来控制进程数量

管道是内核中的一个单向的数据通道,同时也是一个数据队列。具有一个读取端与一个写入端,每一端对应着一个文件描述符。
命名管道即FIFO文件,通过命名管道可以在不相关的进程之间交换数据。FIFO有路径名与之相关联,以一种特殊设备文件形式存在于文件系统中。

FIFO有两种用途:

• FIFO由shell使用以便数据从一条管道线传输到另一条,为此无需创建临时文件,常见的操作cat file|grep keyword就是这种使用方式;
• FIFO用于客户进程-服务器进程程序中,已在客户进程与服务器进程之间传送数据,下面的例子将使用这种方式。

根据FIFO文件的读规则(参考http://www.cnblogs.com/yxmx/articles/1599187.html),如果有进程写打开FIFO,且当前FIFO内没有数据,对于设置了阻塞标志的读操作来说,将一直阻塞状态。

利用这一特性可以实现一个令牌机制。设置一个行数等于限定最大进程数Nproc的fifo文件,在for循环中设置创建一个进程时先read一次fifo文件,进程结束时再write一次fifo文件。如果当前子进程数达到限定最大进程数Nproc,则fifo文件为空,后续执行的并发进程被读fifo命令阻塞,循环内容被没有触发,直至有某一个并发进程执行结果并做写操作(相当于将令牌还给池子)。

需要注意的是,当并发数较大时,多个并发进程即使在使用sleep相同秒数模拟时,也会存在进程调度的顺序问题,因而并不是按启动顺序结束的,可能会后启动的进程先结束。

#!/bin/bash

Njob=15 #任务总数

Nproc=5 #最大并发进程数

mkfifo ./fifo.$$ && exec   9<>  ./fifo.$$     #通过文件描述符777访问fifo文件

for ((i=0; i<$Nproc; i++)); do  #向fifo文件先填充等于Nproc值的行数
  echo  "init time add $i" >&9
done
for ((i=0; i<$Njob; i++)); do
{
  read  -u  9             #从fifo文件读一行
  echo  "progress $i is sleeping for 3 seconds zzz…"
  sleep  3
  echo  "real time add $(($i+$Nproc))"  1>&9 #sleep完成后,向fifo文件重新写入一行
} &
done
wait
echo -e "time-consuming: $SECONDS seconds"
rm -f ./fifo.$$

执行结果

progress 0 is sleeping for 3 seconds zzz…
progress 1 is sleeping for 3 seconds zzz…
progress 2 is sleeping for 3 seconds zzz…
progress 3 is sleeping for 3 seconds zzz…
progress 4 is sleeping for 3 seconds zzz…
progress 5 is sleeping for 3 seconds zzz…
progress 6 is sleeping for 3 seconds zzz…
progress 8 is sleeping for 3 seconds zzz…
progress 12 is sleeping for 3 seconds zzz…
progress 13 is sleeping for 3 seconds zzz…
progress 9 is sleeping for 3 seconds zzz…
progress 11 is sleeping for 3 seconds zzz…
progress 14 is sleeping for 3 seconds zzz…
progress 10 is sleeping for 3 seconds zzz…
progress 7 is sleeping for 3 seconds zzz…
-e time-consuming: 10 seconds

原文地址:
Shell脚本实现并发多进程
Shell脚本并发执行

OKR 填写指南

一、OKR概述
OKR是一个目标管理工具。其中O指Objective,是团队或个人的工作目标;KR指Key Result,是一系列可以衡量的关键结果,用来判断Objective是否达成。在作业帮,OKR的制定和共享,是公司、团队和个人制定任务,对齐目标,协调和集中精力的重要手段。

二、OKR执行要点
工作目标设置应该激进,要使自己和团队感受到压力。
关键结果要容易打分衡量,不要模棱两可。
除保密事项外,OKR尽可能的公开,方便互相了解在忙些什么工作。
写好OKR,要求严格的聚焦,只写最重要的目标,不罗列堆砌。
OKR并不是绩效考核工具,但应该是自我检测工作成果的重要工具。
OKR不是一个共享的工作清单或者to-do list,而是一个管理精力,自我规划的重要工具。
没有经过沟通和对齐的OKR,相当于没有写,OKR的对齐方包括你的上级、同事和你承接需求(提出需求)的协同方。

三、设定激进、聚焦的目标

在每个OKR周期中,公司从CEO到全体员工都会制定自己的OKR,通常是三四个Objectives,每个Objective有3个左右的Key Results。这些OKR既有从上到下目标分解而来的,也有从基层收集的各种意见演化而来,所以OKR的制定是一个循环迭代和讨论的过程。

我们希望每一个人都制定激进的目标,这些目标看上去刚好处于 “这个周期完不成” 的边缘。事实证明,制定高目标,有助于我们取得优于普通水准的成果。 每个高目标都需要全情投入。所以OKR制定的另一方面,是严格地聚焦到重点方向,不贪多。但在选定的重点方向上,力求保证目标达成。

一些设定Objective的窍门:

选三五个目标即可,宁缺勿滥。太多目标,工作容易失去焦点,团队也会疲于应付。
描述最终状态,比如“上线xx功能”,“获得10%的市场份额”。
常规动作不要写进OKR,例如“继续推进”、“保持行业地位”。如果确是长期重要工作,要思考如何拆解成合理的周期OKR,而不是不经思考地写“继续做”。
团队leader是团队的大脑,更要注意排优先级和授权。

四、可衡量的Key Result

关键结果(Key Result,或简称KR)是用来评价目标是否达成的。考虑到这个功能,对关键结果最大的要求就是容易衡量,且直接支持目标的达成。

一些设定Key Result的窍门:

每个目标定3个左右KR即可,不多刻意多写。
注意,KR是用来衡量Objective的达成程度的,问一下自己“这个KR和相应的Objective有直接的支撑关系吗”。
要描述产出,而不是动作。产出是指类似于“发表一篇论文”这样的表述,而动作是指“进行研究并撰写论文”这样的表述。所以当一个KR中出现 “参与”、“分析”、“辅助”这样的词汇,或者描述过程的动词特别多时,可能就有问题了。
用客观、外部可观测和不模糊的表述。最好是你的同事也能够准确地对你的KR进行打分,这说明KR足够清晰。
五、常见错误
执行不好的OKR,不但对公司、团队和个人没有帮助,反而可能造成团队安于现状,甚至方向混淆、内耗。所以我们要避免常见的OKR执行错误,比如下面这些:

堆砌不重要的目标
OKR常常被写得很长、很多,很难用两三句阐述其中的重点。往往这样的OKR包含大量的常规工作。常规工作不是不可以写,而是要判断,这项工作需要我和团队付出额外的努力才能达成吗。因为OKR要求激进,“担心有可能完不成”,相当多的常规目标是达不到这个要求的。这里有两点需要澄清:一、OKR不是工作量的衡量工具,并且我们应当主要关注OKR对业务带来的实际效果,所以不必在OKR中堆砌工作量;二、避免堆砌,要求每个人有判断力,自觉抛弃低优先级的目标和低价值的目标,而将精力和资源放到高优先级目标上。可以问自己几个问题,“这件事不做有什么影响吗”,“这件事做了有实际的业务收益吗”。

只写一个单词
写OKR时容易偷懒,只写一个单词,比如“收入” “DAU” “xx项目”。这样的写法,既没有说明最终要达到的状态,也很难客观打分。同时在沟通和对齐的时候,其他同事也很难看懂。类似的,“其他项目”、“重点项目”也是不可取的Objective,因为项目真的重要话,应该明确说出来要达到什么目标;而归为“其他”的目标,通常可能并不重要。

给自己留余量
如果历史上所有的OKR都轻松达成,可能定目标的时候就不够有雄心。

六、沟通、对齐和进度更新
沟通和对齐有几个重要的作用。首先是在重要的方向上,配合团队和上下级形成合力,而不要往不同方向使力。其次,OKR的制定是一个迭代过程,从草稿到定稿,需要方方面面的意见输入。再次,要了解配合团队定OKR时的激进程度,判断如果配合团队的OKR只能完成0.5分,会不会影响自己的工作。另一方面,应该鼓励经常更新OKR进度,既是对自己的提醒鞭策,又是向同事同步信息的好方式。